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

add easyblock to install OpenSSL wrapper for OpenSSL installed in OS, or build and install OpenSSL from source if not available in OS #2429

Merged
merged 29 commits into from
May 20, 2021
Merged
Changes from 8 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1c87739
add easyblock for OpenSSL wrapper
lexming May 14, 2021
4069cc9
add support for darwin to openssl_wrapper
lexming May 15, 2021
9feaf55
explictly symlink all installations files in openssl_wrapper
lexming May 15, 2021
9ddc4e0
base openss_wrapper on bundle
lexming May 15, 2021
5716375
add wrap_system_openssl extra option to openssl_wrapper
lexming May 15, 2021
8584dba
wrap and check engines libraries in openssl_wrapper
lexming May 16, 2021
ea5d23d
update description of openssl_wrapper
lexming May 16, 2021
88d8442
fix formatting of openssl_wrapper
lexming May 16, 2021
208aed1
minor fixes, tweaks and enhancements for OpenSSL wrapper easyblock
boegel May 16, 2021
e159abd
fix hunt for opensslv.h header file on Ubuntu
boegel May 16, 2021
9a554c1
Merge pull request #4 from boegel/sslwrap
lexming May 16, 2021
6a756f4
make sure OpenSSL version is used in tests for OpenSSL wrapper easyblock
boegel May 16, 2021
bcbe037
limit linked libraries in openssl_wrapper to known libraries
lexming May 17, 2021
4101ba3
add support for headers in openssl11 subfolder in openssl_wrapper
lexming May 17, 2021
25d718c
add support for openssl11 binary to openssl_wrapper
lexming May 17, 2021
0e246a7
set LC_ALL in gcc in openssl_wrapper
lexming May 17, 2021
1a98bed
add comment about library names covered in openssl_wrapper
lexming May 17, 2021
2897480
fix checking OpenSSL version in opensslv.h, create relative symlink f…
boegel May 17, 2021
f07cc40
leverage locate_solib from framework systemtools module
boegel May 17, 2021
6ff9830
fix copyright of openssl_wrapper
lexming May 17, 2021
5701d61
add support for multiple target libs per version and system in openss…
lexming May 17, 2021
116c027
remove import on locate_solib from openssl_wrapper
lexming May 17, 2021
724ea6e
convert zip output to a list in openssl_wrapper
lexming May 18, 2021
9a9f34a
use list comprehension to reduce matrix of libraries in openssl_wrapper
lexming May 18, 2021
fc878fe
only reduce openssl_libs dimension in init of openssl_wrapper
lexming May 19, 2021
fd425a2
use specific attributes for list of installed libs and engines dir
lexming May 19, 2021
86c151f
remove unused variable openssl_engine
lexming May 19, 2021
44193cd
more logging in OpenSSL wrapper, actually fall back to OpenSSL in Eas…
boegel May 20, 2021
1bb2d39
Merge pull request #5 from boegel/sslwrap
lexming May 20, 2021
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
241 changes: 241 additions & 0 deletions easybuild/easyblocks/o/openssl_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
##
# Copyright 2018-2021 Ghent University
#
# This file is part of EasyBuild,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# https://github.com/easybuilders/easybuild
#
# EasyBuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# EasyBuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
##
"""
EasyBuild support for installing a wrapper module file for OpenSSL

@author: Alex Domingo (Vrije Universiteit Brussel)
"""
import ctypes
import ctypes.macholib.dyld
boegel marked this conversation as resolved.
Show resolved Hide resolved
import os
import re

from easybuild.easyblocks.generic.bundle import Bundle
from easybuild.framework.easyconfig import CUSTOM
from easybuild.tools.filetools import expand_glob_paths, mkdir, read_file, symlink, which
from easybuild.tools.run import run_cmd
from easybuild.tools.systemtools import DARWIN, LINUX, get_os_type, get_shared_lib_ext
from easybuild.tools.build_log import EasyBuildError, print_warning


def locate_solib(libobj):
boegel marked this conversation as resolved.
Show resolved Hide resolved
"""
Return absolute path to loaded library using dlinfo
Based on https://stackoverflow.com/a/35683698
"""
class LINKMAP(ctypes.Structure):
_fields_ = [
("l_addr", ctypes.c_void_p),
("l_name", ctypes.c_char_p)
]

libdl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('dl'))

dlinfo = libdl.dlinfo
dlinfo.argtypes = ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p
dlinfo.restype = ctypes.c_int

libpointer = ctypes.c_void_p()
dlinfo(libobj._handle, 2, ctypes.byref(libpointer))
libpath = ctypes.cast(libpointer, ctypes.POINTER(LINKMAP)).contents.l_name
boegel marked this conversation as resolved.
Show resolved Hide resolved

return libpath


class EB_OpenSSL_wrapper(Bundle):
"""
Locate the installation files of OpenSSL in the host system.
If available, wrap the system OpenSSL by symlinking all installation files
Fall back to the bundled component otherwise.
"""

@staticmethod
def extra_options(extra_vars=None):
"""Easyconfig parameters specific to OpenSSL wrapper"""
extra_vars = Bundle.extra_options(extra_vars=extra_vars)
extra_vars.update({
'wrap_system_openssl': [True, 'Detect and wrap OpenSSL installation in host system', CUSTOM],
})
return extra_vars

def __init__(self, *args, **kwargs):
"""Locate the installation files of OpenSSL in the host system"""
super(EB_OpenSSL_wrapper, self).__init__(*args, **kwargs)

# Libraries packaged in OpenSSL
self.openssl_libs = ['libssl', 'libcrypto']
self.openssl_engines = {
'1.0': 'engines',
'1.1': 'engines-1.1',
}

# Paths to system libraries and headers of OpenSSL
self.ssl_syslib = None
self.ssl_sysheader = None
self.ssl_sysengines = None

if not self.cfg.get('wrap_system_openssl'):
return

# Check the system libraries of OpenSSL
libssl = {
'1.0': {LINUX: 'libssl.so.10', DARWIN: 'libssl.1.0.dylib'},
'1.1': {LINUX: 'libssl.so.1.1', DARWIN: 'libssl.1.1.dylib'},
}

os_type = get_os_type()

try:
libssl_so = libssl[self.version][os_type]
libssl_obj = ctypes.cdll.LoadLibrary(libssl_so)
except OSError:
self.log.debug("Library '%s' not found in host system", libssl_so)
else:
# ctypes.util.find_library only accepts unversioned library names
if os_type == LINUX:
# find path to library with dlinfo
self.ssl_syslib = locate_solib(libssl_obj)
elif os_type == DARWIN:
# ctypes.macholib.dyld.dyld_find accepts file names and returns full path
self.ssl_syslib = ctypes.macholib.dyld.dyld_find(libssl_so)
else:
raise EasyBuildError("Unknown host system type: %s", os_type)

self.log.debug("Found library '%s' in: %s", libssl_so, self.ssl_syslib)

# Directory with engine libraries
if self.ssl_syslib:
lib_dir = os.path.dirname(self.ssl_syslib)
lib_engines_dir = [
os.path.join(lib_dir, self.openssl_engines[self.version]),
os.path.join(lib_dir, 'openssl', self.openssl_engines[self.version]),
]

for engines_path in lib_engines_dir:
if os.path.isdir(engines_path):
self.ssl_sysengines = engines_path
self.log.debug("Found OpenSSL engines in: %s", self.ssl_sysengines)

if not self.ssl_sysengines:
self.ssl_syslib = None
print_warning("Found OpenSSL in host system, but not its engines."
"Falling back to OpenSSL in EasyBuild")

# Check system include paths for OpenSSL headers
cmd = "gcc -E -Wp,-v -xc /dev/null"
lexming marked this conversation as resolved.
Show resolved Hide resolved
(out, ec) = run_cmd(cmd, log_all=True, simple=False, trace=False)

sys_include_dirs = []
for match in re.finditer(r'^\s(/[^\0\n]*)+', out, re.MULTILINE):
sys_include_dirs.extend(match.groups())
lexming marked this conversation as resolved.
Show resolved Hide resolved
self.log.debug("Found the following include directories in host system: %s", ', '.join(sys_include_dirs))

# headers are always in "include/openssl" subdirectories
ssl_include_dirs = [os.path.join(include, self.name.lower()) for include in sys_include_dirs]
ssl_include_dirs = [include for include in ssl_include_dirs if os.path.isdir(include)]

# verify that the headers match our OpenSSL version
for include in ssl_include_dirs:
opensslv = read_file(os.path.join(include, 'opensslv.h'))
header_majmin_version = re.search("SHLIB_VERSION_NUMBER\s\"([0-9]+\.[0-9]+)", opensslv, re.M)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this regex seems fine to me

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use a raw string here:

Suggested change
header_majmin_version = re.search("SHLIB_VERSION_NUMBER\s\"([0-9]+\.[0-9]+)", opensslv, re.M)
header_majmin_version = re.search(r"SHLIB_VERSION_NUMBER\s\"([0-9]+\.[0-9]+)", opensslv, re.M)

if re.match("^{}".format(*header_majmin_version.groups()), self.version):
self.ssl_sysheader = include
self.log.debug("Found OpenSSL headers in host system: %s", self.ssl_sysheader)

if not self.ssl_sysheader:
self.log.debug("OpenSSL headers not found in host system")

def fetch_step(self, *args, **kwargs):
"""Fetch sources if OpenSSL component is needed"""
if not all([self.ssl_syslib, self.ssl_sysheader]):
super(EB_OpenSSL_wrapper, self).fetch_step(*args, **kwargs)

def extract_step(self):
"""Extract sources if OpenSSL component is needed"""
if not all([self.ssl_syslib, self.ssl_sysheader]):
super(EB_OpenSSL_wrapper, self).extract_step()

def install_step(self):
"""Symlink target OpenSSL installation"""
shlib_ext = get_shared_lib_ext()

if self.ssl_syslib and self.ssl_sysheader:
# The host system already provides the necessary OpenSSL files
lib_pattern = ['%s*.%s*' % (lib_so, shlib_ext) for lib_so in self.openssl_libs]
lib_pattern = [os.path.join(os.path.dirname(self.ssl_syslib), '%s' % ptrn) for ptrn in lib_pattern]
lexming marked this conversation as resolved.
Show resolved Hide resolved
lib_pattern.append(os.path.join(self.ssl_sysengines, '*'))

include_pattern = [os.path.join(self.ssl_sysheader, '*')]

bin_path = which(self.name.lower())
lexming marked this conversation as resolved.
Show resolved Hide resolved

# Link OpenSSL libraries
lib64_dir = os.path.join(self.installdir, 'lib64')
lib64_engines_dir = os.path.join(lib64_dir, os.path.basename(self.ssl_sysengines))
mkdir(lib64_engines_dir, parents=True)
symlink('lib64', 'lib')

lib_files = expand_glob_paths(lib_pattern)

for libso in lib_files:
if 'engines' in libso:
target_dir = lib64_engines_dir
else:
target_dir = lib64_dir
symlink(libso, os.path.join(target_dir, os.path.basename(libso)))

# Link OpenSSL headers
include_dir = os.path.join(self.installdir, 'include', self.name.lower())
mkdir(include_dir, parents=True)

include_files = expand_glob_paths(include_pattern)
for header in include_files:
symlink(header, os.path.join(include_dir, os.path.basename(header)))
boegel marked this conversation as resolved.
Show resolved Hide resolved

# Link OpenSSL binary
bin_dir = os.path.join(self.installdir, 'bin')
mkdir(bin_dir)
symlink(bin_path, os.path.join(bin_dir, os.path.basename(bin_path)))
lexming marked this conversation as resolved.
Show resolved Hide resolved

else:
# Install OpenSSL component
super(EB_OpenSSL_wrapper, self).install_step()

def sanity_check_step(self):
"""Custom sanity check for OpenSSL wrapper."""
shlib_ext = get_shared_lib_ext()

ssl_files = [os.path.join('lib', '%s.%s' % (libso, shlib_ext)) for libso in self.openssl_libs]
ssl_files.append(os.path.join('bin', self.name.lower()))

ssl_dirs = [os.path.join('include', self.name.lower())]
ssl_dirs.append(os.path.join('lib', self.openssl_engines[self.version]))

custom_paths = {
'files': ssl_files,
'dirs': ssl_dirs,
}

super(Bundle, self).sanity_check_step(custom_paths=custom_paths)