Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IGNITE-14631 SSL certificates generation via python code #9038

Merged
merged 12 commits into from
Apr 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@
"""
Check that SslParams correctly parse SSL params from globals
"""
import os

import pytest
from ignitetest.services.utils.ssl.ssl_params import get_ssl_params, SslParams, DEFAULT_TRUSTSTORE, \
DEFAULT_CLIENT_KEYSTORE, DEFAULT_PASSWORD, IGNITE_CLIENT_ALIAS, SSL_KEY, SSL_PARAMS_KEY, ENABLED_KEY

INSTALL_ROOT = '/opt/'
CERTIFICATE_DIR = '/opt/ignite-dev/modules/ducktests/tests/certs/'
CERT_DIR1 = "/folder1"
CERT_DIR2 = "/folder2"
TEST_KEYSTORE_JKS = "client1.jks"
TEST_TRUSTSTORE_JKS = "truststore.jks"
TEST_PASSWORD = "qwe123"
TEST_CERTIFICATE_DIR = "/opt/certs/"


def _compare(expected, actual):
Expand Down Expand Up @@ -72,7 +72,6 @@ class TestParams:
"""

test_globals_jks = {
"install_root": INSTALL_ROOT,
SSL_KEY: {
ENABLED_KEY: True,
SSL_PARAMS_KEY: {
Expand All @@ -82,32 +81,28 @@ class TestParams:
"trust_store_jks": TEST_TRUSTSTORE_JKS,
"trust_store_password": TEST_PASSWORD}}}}
test_globals_path = {
"install_root": INSTALL_ROOT,
SSL_KEY: {
ENABLED_KEY: True,
SSL_PARAMS_KEY: {
IGNITE_CLIENT_ALIAS: {
"key_store_path": TEST_CERTIFICATE_DIR + TEST_KEYSTORE_JKS,
"key_store_path": os.path.join(CERT_DIR2, TEST_KEYSTORE_JKS),
"key_store_password": TEST_PASSWORD,
"trust_store_path": TEST_CERTIFICATE_DIR + TEST_TRUSTSTORE_JKS,
"trust_store_path": os.path.join(CERT_DIR2, TEST_TRUSTSTORE_JKS),
"trust_store_password": TEST_PASSWORD}}}}
test_globals_default = {
"install_root": INSTALL_ROOT,
SSL_KEY: {ENABLED_KEY: True}}
test_globals_no_ssl = {
"install_root": INSTALL_ROOT}
test_globals_default = {SSL_KEY: {ENABLED_KEY: True}}
test_globals_no_ssl = {}

expected_ssl_params_jks = {'key_store_path': CERTIFICATE_DIR + TEST_KEYSTORE_JKS,
expected_ssl_params_jks = {'key_store_path': os.path.join(CERT_DIR1, TEST_KEYSTORE_JKS),
'key_store_password': TEST_PASSWORD,
'trust_store_path': CERTIFICATE_DIR + TEST_TRUSTSTORE_JKS,
'trust_store_path': os.path.join(CERT_DIR1, TEST_TRUSTSTORE_JKS),
'trust_store_password': TEST_PASSWORD}
expected_ssl_params_path = {'key_store_path': TEST_CERTIFICATE_DIR + TEST_KEYSTORE_JKS,
expected_ssl_params_path = {'key_store_path': os.path.join(CERT_DIR2, TEST_KEYSTORE_JKS),
'key_store_password': TEST_PASSWORD,
'trust_store_path': TEST_CERTIFICATE_DIR + TEST_TRUSTSTORE_JKS,
'trust_store_path': os.path.join(CERT_DIR2, TEST_TRUSTSTORE_JKS),
'trust_store_password': TEST_PASSWORD}
expected_ssl_params_default = {'key_store_path': CERTIFICATE_DIR + DEFAULT_CLIENT_KEYSTORE,
expected_ssl_params_default = {'key_store_path': os.path.join(CERT_DIR1, DEFAULT_CLIENT_KEYSTORE),
'key_store_password': DEFAULT_PASSWORD,
'trust_store_path': CERTIFICATE_DIR + DEFAULT_TRUSTSTORE,
'trust_store_path': os.path.join(CERT_DIR1, DEFAULT_TRUSTSTORE),
'trust_store_password': DEFAULT_PASSWORD}


Expand All @@ -117,12 +112,12 @@ class CheckCaseJks:
"""

@staticmethod
@pytest.mark.parametrize('test_globals, expected',
[(TestParams.test_globals_jks, TestParams.expected_ssl_params_jks),
(TestParams.test_globals_path, TestParams.expected_ssl_params_path),
(TestParams.test_globals_default, TestParams.expected_ssl_params_default)])
def check_parse(test_globals, expected):
@pytest.mark.parametrize('test_globals, shared_root, expected',
[(TestParams.test_globals_jks, CERT_DIR1, TestParams.expected_ssl_params_jks),
(TestParams.test_globals_path, CERT_DIR2, TestParams.expected_ssl_params_path),
(TestParams.test_globals_default, CERT_DIR1, TestParams.expected_ssl_params_default)])
def check_parse(test_globals, shared_root, expected):
"""
Check that SslParams correctly parse SSL params from globals
"""
assert _compare(expected, get_ssl_params(test_globals, IGNITE_CLIENT_ALIAS))
assert _compare(expected, get_ssl_params(test_globals, shared_root, IGNITE_CLIENT_ALIAS))
2 changes: 0 additions & 2 deletions modules/ducktests/tests/docker/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,5 @@ if [[ -n "$MAX_PARALLEL" ]]; then
DUCKTAPE_OPTIONS="$DUCKTAPE_OPTIONS --max-parallel $MAX_PARALLEL"
fi

"$SCRIPT_DIR"/../certs/mkcerts.sh

"$SCRIPT_DIR"/ducker-ignite test "$TC_PATHS" "$DUCKTAPE_OPTIONS" \
|| die "ducker-ignite test failed"
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def __init__(self, cluster, ssl_params=None, username=None, password=None):
if ssl_params:
self.ssl_params = ssl_params
elif is_ssl_enabled(cluster.context.globals):
self.ssl_params = get_ssl_params(cluster.context.globals, IGNITE_ADMIN_ALIAS)
self.ssl_params = get_ssl_params(cluster.context.globals, cluster.shared_root, IGNITE_ADMIN_ALIAS)

if username and password:
self.username, self.password = username, password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def await_started(self):
self.await_event("Topology snapshot", self.startup_timeout_sec, from_the_beginning=True)

def start_node(self, node, **kwargs):
self.init_shared(node)
self.init_persistent(node)

self.__update_node_log_file(node)
Expand Down Expand Up @@ -171,8 +172,25 @@ def init_persistent(self, node):

self._prepare_configs(node)

def init_shared(self, node):
"""
Init shared directory. Content of shared directory must be equal on all test nodes.
:param node: Ignite service node.
"""
local_shared_dir = self.spec.init_local_shared()

if not os.path.isdir(local_shared_dir):
self.logger.debug("Local shared dir not exists. Nothing to copy. " + str(local_shared_dir))
return

node.account.mkdirs(f"{self.persistent_root} {self.shared_root}")

for file in os.listdir(local_shared_dir):
self.logger.debug("Copying shared file to node. " + str(file))
node.account.copy_to(os.path.join(local_shared_dir, file), self.shared_root)

def _prepare_configs(self, node):
config = self.config.prepare_for_env(test_globals=self.globals, node=node, cluster=self)
config = self.config.prepare_for_env(self.globals, self.shared_root, node, self)

for name, template in self.spec.config_templates:
config_txt = template.render(config_dir=self.config_dir, work_dir=self.work_dir, config=config)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,15 @@ class IgniteConfiguration(NamedTuple):
rebalance_batches_prefetch_count: int = None
rebalance_throttle: int = None

def __prepare_ssl(self, test_globals):
def __prepare_ssl(self, test_globals, shared_root):
"""
Updates ssl configuration from globals.
"""
ssl_params = None
if self.ssl_params is None and is_ssl_enabled(test_globals):
ssl_params = get_ssl_params(
test_globals,
shared_root,
IGNITE_CLIENT_ALIAS if self.client_mode else IGNITE_SERVER_ALIAS
)
if ssl_params:
Expand All @@ -89,11 +90,11 @@ def __prepare_discovery(self, node, cluster):
return config

# pylint: disable=protected-access
def prepare_for_env(self, test_globals, node, cluster):
def prepare_for_env(self, test_globals, shared_root, node, cluster):
"""
Updates configuration based on current environment.
"""
return self.__prepare_ssl(test_globals).__prepare_discovery(node, cluster)
return self.__prepare_ssl(test_globals, shared_root).__prepare_discovery(node, cluster)

@property
def service_type(self):
Expand All @@ -118,7 +119,7 @@ class IgniteThinClientConfiguration(NamedTuple):
version: IgniteVersion = DEV_BRANCH

# pylint: disable=unused-argument
def prepare_for_env(self, test_globals, node, cluster):
def prepare_for_env(self, test_globals, shared_root, node, cluster):
"""
Updates configuration based on current environment.
"""
Expand Down
36 changes: 36 additions & 0 deletions modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
import importlib
import json
import os
import subprocess
import tempfile
from abc import ABCMeta, abstractmethod

from ignitetest.services.utils import IgniteServiceType
from ignitetest.services.utils.config_template import IgniteClientConfigTemplate, IgniteServerConfigTemplate, \
IgniteLoggerConfigTemplate, IgniteThinClientConfigTemplate
from ignitetest.services.utils.jvm_utils import create_jvm_settings, merge_jvm_settings
from ignitetest.services.utils.path import get_home_dir, get_module_path, IgnitePathAware
from ignitetest.services.utils.ssl.ssl_params import is_ssl_enabled
from ignitetest.utils.version import DEV_BRANCH


Expand Down Expand Up @@ -157,6 +160,31 @@ def config_file_path(self):
"""
return self.service.config_file

def init_local_shared(self):
"""
:return: path to local share folder. Files should be copied on all nodes in `shared_root` folder.
"""
local_dir = os.path.join(tempfile.gettempdir(), str(self.service.context.session_context.session_id))

if not is_ssl_enabled(self.service.context.globals) and \
not (self.service.config.service_type == IgniteServiceType.NODE and self.service.config.ssl_params):
self.service.logger.debug("Ssl disabled. Nothing to generate.")
return local_dir

if os.path.isdir(local_dir):
self.service.logger.debug("Local shared dir already exists. Exiting. " + local_dir)
return local_dir

self.service.logger.debug("Local shared dir not exists. Creating. " + local_dir)
os.mkdir(local_dir)

script_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "certs")

self.__runcmd(f"cp {script_dir}/*.sh {local_dir}")
self.__runcmd(f"{local_dir}/mkcerts.sh")

return local_dir

def _jvm_opts(self):
"""
:return: line with extra JVM params for ignite.sh script: -J-Dparam=value -J-ea
Expand All @@ -168,6 +196,14 @@ def _add_jvm_opts(self, opts):
"""Properly adds JVM options to current"""
self.jvm_opts = merge_jvm_settings(self.jvm_opts, opts)

def __runcmd(self, cmd):
self.service.logger.debug(cmd)
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, _ = proc.communicate()

if proc.returncode != 0:
raise RuntimeError("Command '%s' returned non-zero exit status %d: %s" % (cmd, proc.returncode, stdout))


class IgniteNodeSpec(IgniteSpec):
"""
Expand Down
7 changes: 7 additions & 0 deletions modules/ducktests/tests/ignitetest/services/utils/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ def log_dir(self):
"""
return os.path.join(self.persistent_root, "logs")

@property
def shared_root(self):
"""
:return: path to directory with shared files - same files on all nodes
"""
return os.path.join(self.persistent_root, "shared")

@property
@abstractmethod
def product(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
DEFAULT_ADMIN_KEYSTORE = 'admin.jks'
DEFAULT_PASSWORD = "123456"
DEFAULT_TRUSTSTORE = "truststore.jks"
DEFAULT_ROOT = "/opt/"

SSL_PARAMS_KEY = "params"
SSL_KEY = "ssl"
Expand All @@ -46,21 +45,19 @@ class SslParams:
"""

# pylint: disable=R0913
def __init__(self, key_store_jks: str = None, key_store_password: str = DEFAULT_PASSWORD,
def __init__(self, root_dir: str, key_store_jks: str = None, key_store_password: str = DEFAULT_PASSWORD,
trust_store_jks: str = DEFAULT_TRUSTSTORE, trust_store_password: str = DEFAULT_PASSWORD,
key_store_path: str = None, trust_store_path: str = None, root_dir: str = DEFAULT_ROOT):
key_store_path: str = None, trust_store_path: str = None):
if not key_store_jks and not key_store_path:
raise Exception("Keystore must be specified to init SslParams")

certificate_dir = os.path.join(root_dir, "ignite-dev", "modules", "ducktests", "tests", "certs")

self.key_store_path = key_store_path if key_store_path else os.path.join(certificate_dir, key_store_jks)
self.key_store_path = key_store_path if key_store_path else os.path.join(root_dir, key_store_jks)
self.key_store_password = key_store_password
self.trust_store_path = trust_store_path if trust_store_path else os.path.join(certificate_dir, trust_store_jks)
self.trust_store_path = trust_store_path if trust_store_path else os.path.join(root_dir, trust_store_jks)
self.trust_store_password = trust_store_password


def get_ssl_params(_globals: dict, alias: str):
def get_ssl_params(_globals: dict, shared_root: str, alias: str):
"""
Gets SSL params from Globals
Structure may be found in modules/ducktests/tests/checks/utils/check_get_ssl_params.py
Expand All @@ -79,15 +76,14 @@ def get_ssl_params(_globals: dict, alias: str):
If you specyfy ssl_params in test, you override globals
"""

root_dir = _globals.get("install_root", DEFAULT_ROOT)
if SSL_PARAMS_KEY in _globals[SSL_KEY] and alias in _globals[SSL_KEY][SSL_PARAMS_KEY]:
ssl_param = _globals[SSL_KEY][SSL_PARAMS_KEY][alias]
elif alias in default_keystore:
ssl_param = {'key_store_jks': default_keystore[alias]}
else:
raise Exception("We don't have SSL params for: " + alias)

return SslParams(root_dir=root_dir, **ssl_param) if ssl_param else None
return SslParams(shared_root, **ssl_param) if ssl_param else None


def is_ssl_enabled(_globals: dict):
Expand Down
8 changes: 4 additions & 4 deletions modules/ducktests/tests/ignitetest/tests/ssl_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def test_ssl_connection(self, ignite_version):
Test that IgniteService, IgniteApplicationService correctly start and stop with ssl configurations.
And check ControlUtility with ssl arguments.
"""
root_dir = self.test_context.globals.get("install_root", "/opt")
shared_root = "/mnt/service/shared"

server_ssl = SslParams(root_dir=root_dir, key_store_jks=DEFAULT_SERVER_KEYSTORE)
server_ssl = SslParams(shared_root, key_store_jks=DEFAULT_SERVER_KEYSTORE)

server_configuration = IgniteConfiguration(
version=IgniteVersion(ignite_version), ssl_params=server_ssl,
Expand All @@ -54,7 +54,7 @@ def test_ssl_connection(self, ignite_version):

client_configuration = server_configuration._replace(
client_mode=True,
ssl_params=SslParams(root_dir=root_dir, key_store_jks=DEFAULT_CLIENT_KEYSTORE),
ssl_params=SslParams(shared_root, key_store_jks=DEFAULT_CLIENT_KEYSTORE),
connector_configuration=None)

app = IgniteApplicationService(
Expand All @@ -63,7 +63,7 @@ def test_ssl_connection(self, ignite_version):
java_class_name="org.apache.ignite.internal.ducktest.tests.smoke_test.SimpleApplication",
startup_timeout_sec=180)

admin_ssl = SslParams(root_dir=root_dir, key_store_jks=DEFAULT_ADMIN_KEYSTORE)
admin_ssl = SslParams(shared_root, key_store_jks=DEFAULT_ADMIN_KEYSTORE)
control_utility = ControlUtility(cluster=ignite, ssl_params=admin_ssl)

ignite.start()
Expand Down