Support Singularity image encryption

- Modify singularity_images global resources to support encryption
options
- Automatically bind certificates to encrypted containers when a task
executes
This commit is contained in:
Fred Park 2019-11-06 04:14:40 +00:00
Родитель 05a417e673
Коммит 134262158b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 3C4D545F457737EB
11 изменённых файлов: 279 добавлений и 111 удалений

Просмотреть файл

@ -4,7 +4,7 @@ set -e
set -o pipefail set -o pipefail
set -f set -f
privatekey=$AZ_BATCH_NODE_STARTUP_DIR/certs/key.pem privatekey=$AZ_BATCH_NODE_STARTUP_DIR/certs/shipyard-enckey.pem
for spec in "$@"; do for spec in "$@"; do
IFS=',' read -ra parts <<< "$spec" IFS=',' read -ra parts <<< "$spec"

Просмотреть файл

@ -1,7 +1,7 @@
# Dockerfile for Azure/batch-shipyard (Cascade/Singularity) # Dockerfile for Azure/batch-shipyard (Cascade/Singularity)
# base image containing singularity # base image containing Singularity
FROM alfpark/singularity:3.3.0 FROM alfpark/singularity:3.4.2
FROM ubuntu:18.04 FROM ubuntu:18.04
MAINTAINER Fred Park <https://github.com/Azure/batch-shipyard> MAINTAINER Fred Park <https://github.com/Azure/batch-shipyard>
@ -20,6 +20,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libssl-dev \ libssl-dev \
libffi-dev \ libffi-dev \
squashfs-tools \ squashfs-tools \
cryptsetup-bin \
bash \ bash \
&& python3 -m pip install --no-cache-dir --upgrade pip \ && python3 -m pip install --no-cache-dir --upgrade pip \
&& pip3 install --no-cache-dir --upgrade setuptools wheel \ && pip3 install --no-cache-dir --upgrade setuptools wheel \

Просмотреть файл

@ -464,18 +464,19 @@ class ContainerImageSaveThread(threading.Thread):
if username is not None and password is not None: if username is not None and password is not None:
credentials_command_argument = ( credentials_command_argument = (
'--docker-username {} --docker-password {} '.format( '--docker-username {} --docker-password {} '.format(
username, password)) username, password)
)
else: else:
credentials_command_argument = '' credentials_command_argument = ''
if image in _DIRECTDL_KEY_FINGERPRINT_DICT: if image in _DIRECTDL_KEY_FINGERPRINT_DICT:
singularity_pull_cmd = ( singularity_pull_cmd = (
'singularity pull -F ' + 'singularity pull -F ' + credentials_command_argument +
credentials_command_argument + '{} {}'.format(image_out_path, image)
'{} {}'.format(image_out_path, image)) )
key_fingerprint = _DIRECTDL_KEY_FINGERPRINT_DICT[image] key_fingerprint = _DIRECTDL_KEY_FINGERPRINT_DICT[image]
if key_file_path.is_file(): if key_file_path.is_file():
key_import_cmd = ('singularity key import {}' key_import_cmd = 'singularity key import {}'.format(
.format(key_file_path)) key_file_path)
fingerprint_check_cmd = ( fingerprint_check_cmd = (
'key_fingerprint=$({} | '.format(key_import_cmd) + 'key_fingerprint=$({} | '.format(key_import_cmd) +
'grep -o "fingerprint \\(\\S*\\)" | ' + 'grep -o "fingerprint \\(\\S*\\)" | ' +
@ -486,22 +487,19 @@ class ContainerImageSaveThread(threading.Thread):
'key file $key_fingerprint does not match ' + 'key file $key_fingerprint does not match ' +
'fingerprint provided {}")'.format(key_fingerprint) + 'fingerprint provided {}")'.format(key_fingerprint) +
' && exit 1; fi') ' && exit 1; fi')
cmd = (key_import_cmd + ' && ' + fingerprint_check_cmd + cmd = '{} && {} && {}'.format(
' && ' + singularity_pull_cmd) key_import_cmd, fingerprint_check_cmd,
singularity_pull_cmd)
else: else:
key_pull_cmd = ('singularity key pull {}' cmd = '{} && singularity key pull {}'.format(
.format(key_fingerprint)) singularity_pull_cmd, key_fingerprint)
cmd = key_pull_cmd + ' && ' + singularity_pull_cmd # always verify image separately
# if the image pulled from oras we need to manually cmd = '{} && singularity verify {}'.format(cmd, image_out_path)
# verify the image
if image.startswith('oras://'):
singularity_verify_cmd = ('singularity verify {}'
.format(image_out_path))
cmd = cmd + ' && ' + singularity_verify_cmd
else: else:
cmd = ('singularity pull -U -F ' + cmd = (
credentials_command_argument + 'singularity pull -U -F ' + credentials_command_argument +
'{} {}'.format(image_out_path, image)) '{} {}'.format(image_out_path, image)
)
return cmd return cmd
def _pull(self, grtype: str, image: str) -> tuple: def _pull(self, grtype: str, image: str) -> tuple:
@ -535,6 +533,7 @@ class ContainerImageSaveThread(threading.Thread):
while True: while True:
rc, stdout, stderr = self._pull(grtype, image) rc, stdout, stderr = self._pull(grtype, image)
if rc == 0: if rc == 0:
logger.debug(stdout)
break break
elif self._check_pull_output_overload(stderr.lower()): elif self._check_pull_output_overload(stderr.lower()):
logger.error( logger.error(

Просмотреть файл

@ -27,16 +27,29 @@ global_resources:
- myserver.azurecr.io/repo/myimage - myserver.azurecr.io/repo/myimage
singularity_images: singularity_images:
unsigned: unsigned:
- shub://singularityhub/busybox - image: shub://singularityhub/busybox
- shub://singularityhub/scientific-linux - image: docker://busybox
- docker://busybox - image: oras://myazurecr.azurecr.io/repo/myunsignedimage:1.0.0
- image: library://user/repo/image:1.0.0
- image: library://user/repo/encryptedimage:1.0.0
encryption:
certificate:
sha1_thumbprint: 123456789...
signed: signed:
- image: library://sylabs/tests/signed:1.0.0 - image: library://sylabs/tests/signed:1.0.0
key_fingerprint: 8883491F4268F173C6E5DC49EDECE4F3F38D871E signing_key:
key_file: /path/to/key/file fingerprint: 8883491F4268F173C6E5DC49EDECE4F3F38D871E
- image: oras://myazurecr.azurecr.io - image: oras://myazurecr.azurecr.io/repo/mysignedimage:1.0.0
key_fingerprint: 000123000123000123000123000123000123ABCD signing_key:
key_file: /path/to/key/file fingerprint: 000123000123000123000123000123000123ABCD
file: /path/to/key/file
- image: library://user/repo/encryptedimage:1.0.0
signing_key:
fingerprint: 000123000123000123000123000123000123ABCD
file: /path/to/key/file
encryption:
certificate:
sha1_thumbprint: 123456789...
volumes: volumes:
data_volumes: data_volumes:
contdatavol: contdatavol:

Просмотреть файл

@ -1292,16 +1292,17 @@ def _construct_pool_object(
'credentials under batch') 'credentials under batch')
bi_pkg = _setup_batch_insights_package(config, pool_settings) bi_pkg = _setup_batch_insights_package(config, pool_settings)
_rflist.append((bi_pkg.name, bi_pkg)) _rflist.append((bi_pkg.name, bi_pkg))
# singularity settings # singularity images settings
singularity_signed_images_settings = ( singularity_images_settings = (
settings.global_resources_singularity_signed_images_settings(config)) settings.global_resources_singularity_images_settings(config, False) +
for image_settings in singularity_signed_images_settings: settings.global_resources_singularity_images_settings(config, True)
if image_settings.key_file is None: )
continue for image_settings in singularity_images_settings:
key_file_name = util.singularity_image_name_to_key_file_name( if util.is_not_empty(image_settings.key_file):
image_settings.image) key_file_name = util.singularity_image_name_to_key_file_name(
key_file_path = image_settings.key_file image_settings.image)
_rflist.append((key_file_name, key_file_path)) key_file_path = image_settings.key_file
_rflist.append((key_file_name, key_file_path))
# get container registries # get container registries
docker_registries = settings.docker_registries(config) docker_registries = settings.docker_registries(config)
# set additional start task commands (pre version) # set additional start task commands (pre version)
@ -1494,6 +1495,7 @@ def _construct_pool_object(
task_scheduling_policy=task_scheduling_policy, task_scheduling_policy=task_scheduling_policy,
certificate_references=[] certificate_references=[]
) )
# certificate refs
if encrypt: if encrypt:
if is_windows: if is_windows:
pool.certificate_references.append( pool.certificate_references.append(
@ -1514,28 +1516,30 @@ def _construct_pool_object(
visibility=[batchmodels.CertificateVisibility.start_task] visibility=[batchmodels.CertificateVisibility.start_task]
) )
) )
singularity_certs = []
for image_setting in singularity_images_settings:
if util.is_not_empty(
image_setting.encryption_certificate_sha1_thumbprint):
pool.certificate_references.append(
batchmodels.CertificateReference(
thumbprint=image_setting.
encryption_certificate_sha1_thumbprint,
thumbprint_algorithm='sha1',
visibility=[batchmodels.CertificateVisibility.start_task]
)
)
singularity_certs.append(
image_setting.encryption_certificate_sha1_thumbprint)
del singularity_images_settings
if util.is_not_empty(pool_settings.certificates): if util.is_not_empty(pool_settings.certificates):
pool.certificate_references.extend(pool_settings.certificates) pool.certificate_references.extend(pool_settings.certificates)
# resource files
for rf in sas_urls: for rf in sas_urls:
pool.start_task.resource_files.append( pool.start_task.resource_files.append(
batchmodels.ResourceFile( batchmodels.ResourceFile(
file_path=rf, file_path=rf,
http_url=sas_urls[rf]) http_url=sas_urls[rf])
) )
if not native or delay_image_preload:
pool.start_task.environment_settings.append(
batchmodels.EnvironmentSetting(
name='SHIPYARD_STORAGE_ENV',
value=crypto.encrypt_string(
encrypt,
'{}:{}:{}'.format(
storage.get_storageaccount(),
storage.get_storageaccount_endpoint(),
storage.get_storageaccount_key()),
config
)
)
)
if not native: if not native:
if pool_settings.gpu_driver and util.is_none_or_empty(custom_image_na): if pool_settings.gpu_driver and util.is_none_or_empty(custom_image_na):
pool.start_task.resource_files.append( pool.start_task.resource_files.append(
@ -1598,6 +1602,30 @@ def _construct_pool_object(
endpoint_configuration=pec, endpoint_configuration=pec,
public_ips=pool_settings.public_ips, public_ips=pool_settings.public_ips,
) )
# storage env vars
if not native or delay_image_preload:
pool.start_task.environment_settings.append(
batchmodels.EnvironmentSetting(
name='SHIPYARD_STORAGE_ENV',
value=crypto.encrypt_string(
encrypt,
'{}:{}:{}'.format(
storage.get_storageaccount(),
storage.get_storageaccount_endpoint(),
storage.get_storageaccount_key()),
config
)
)
)
# singularity certs
if util.is_not_empty(singularity_certs):
pool.start_task.environment_settings.append(
batchmodels.EnvironmentSetting(
name='SHIPYARD_SINGULARITY_DECRYPTION_CERTIFICATES',
value=','.join(singularity_certs)
)
)
del singularity_certs
# storage cluster settings # storage cluster settings
if util.is_not_empty(sc_fstab_mounts): if util.is_not_empty(sc_fstab_mounts):
pool.start_task.environment_settings.append( pool.start_task.environment_settings.append(
@ -1606,8 +1634,8 @@ def _construct_pool_object(
value='#'.join(sc_fstab_mounts) value='#'.join(sc_fstab_mounts)
) )
) )
del sc_args del sc_args
del sc_fstab_mounts del sc_fstab_mounts
# custom linux mount settings # custom linux mount settings
if util.is_not_empty(custom_linux_fstab_mounts): if util.is_not_empty(custom_linux_fstab_mounts):
pool.start_task.environment_settings.append( pool.start_task.environment_settings.append(
@ -1616,7 +1644,7 @@ def _construct_pool_object(
value='#'.join(custom_linux_fstab_mounts) value='#'.join(custom_linux_fstab_mounts)
) )
) )
del custom_linux_fstab_mounts del custom_linux_fstab_mounts
# add optional environment variables # add optional environment variables
if not native and bs.store_timing_metrics: if not native and bs.store_timing_metrics:
pool.start_task.environment_settings.append( pool.start_task.environment_settings.append(

Просмотреть файл

@ -512,9 +512,10 @@ SlurmCredentialsSettings = collections.namedtuple(
'db_password', 'db_password',
] ]
) )
SingularitySignedImageSettings = collections.namedtuple( SingularityImageSettings = collections.namedtuple(
'SingularitySignedImageSettings', [ 'SingularityImageSettings', [
'image', 'key_fingerprint', 'key_file' 'image', 'key_fingerprint', 'key_file',
'encryption_certificate_sha1_thumbprint'
] ]
) )
@ -2398,62 +2399,74 @@ def global_resources_singularity_images(config):
:rtype: list :rtype: list
:return: all singularity images (signed and unsigned) :return: all singularity images (signed and unsigned)
""" """
global_resources = _kv_read_checked(config, 'global_resources', default={}) singularity_unsigned_images_settings = (
singularity_images = ( global_resources_singularity_images_settings(config, False)
_kv_read_checked(global_resources, 'singularity_images', default={})) )
singularity_unsigned_images = ( singularity_unsigned_images = [
_kv_read_checked(singularity_images, 'unsigned', default=[])) settings.image for settings in singularity_unsigned_images_settings
]
singularity_signed_images_settings = ( singularity_signed_images_settings = (
global_resources_singularity_signed_images_settings(config)) global_resources_singularity_images_settings(config, True)
singularity_signed_images = ( )
[settings.image for settings in singularity_signed_images_settings]) singularity_signed_images = [
settings.image for settings in singularity_signed_images_settings
]
images = singularity_unsigned_images + singularity_signed_images images = singularity_unsigned_images + singularity_signed_images
singularity_signed_and_unsigned_images = ( singularity_signed_and_unsigned_images = (
set(singularity_unsigned_images).intersection( set(singularity_unsigned_images).intersection(
singularity_signed_images)) singularity_signed_images))
if len(singularity_signed_and_unsigned_images): if len(singularity_signed_and_unsigned_images) > 0:
raise ValueError( raise ValueError(
'image(s) "{}" should not be both signed and unsigned' 'image(s) "{}" should not be both signed and unsigned'.format(
.format('", "'.join(singularity_signed_and_unsigned_images))) '", "'.join(singularity_signed_and_unsigned_images)))
return images return images
def global_resources_singularity_signed_images_settings(config): def global_resources_singularity_images_settings(config, signed):
# type: (dict) -> list # type: (dict, bool) -> list
"""Get list of singularity signed images settings """Get list of singularity images settings
:param dict config: configuration object :param dict config: configuration object
:param bool signed: get signed images if True, else unsigned images
:rtype: list :rtype: list
:return: singularity signed images settings :return: singularity images settings
""" """
global_resources = _kv_read_checked(config, 'global_resources', default={}) global_resources = _kv_read_checked(config, 'global_resources', default={})
singularity_images = _kv_read_checked( images = _kv_read_checked(
global_resources, 'singularity_images', default={}) global_resources, 'singularity_images', default={})
singularity_signed_images = _kv_read_checked( singularity_images = _kv_read_checked(
singularity_images, 'signed', default=[]) images, 'signed' if signed else 'unsigned', default=[])
singularity_signed_images_settings = [] singularity_images_settings = []
for settings in singularity_signed_images: for settings in singularity_images:
image = _kv_read_checked(settings, 'image') image = _kv_read_checked(settings, 'image')
if image is None: if util.is_none_or_empty(image):
raise ValueError('singularity signed image is invalid') raise ValueError('singularity image is invalid')
key_fingerprint = _kv_read_checked(settings, 'key_fingerprint') key_fingerprint = None
if key_fingerprint is None:
raise ValueError('key_fingerprint for singularity signed image'
' "{}" is invalid'.format(image))
key_file = _kv_read_checked(settings, 'key_file')
key_file_path = None key_file_path = None
if key_file is not None: if signed:
key_file_path = pathlib.Path(key_file) key = _kv_read_checked(settings, 'signing_key', default={})
if not key_file_path.is_file(): key_fingerprint = _kv_read_checked(key, 'fingerprint')
raise ValueError('invalid key file for image "{}"' if util.is_none_or_empty(key_fingerprint):
.format(image)) raise ValueError(
singularity_signed_images_settings.append( 'key_fingerprint for singularity signed image "{}" is '
SingularitySignedImageSettings( 'invalid'.format(image))
key_file = _kv_read_checked(key, 'file')
if key_file is not None:
key_file_path = pathlib.Path(key_file)
if not key_file_path.is_file():
raise ValueError(
'invalid key file for image "{}"'.format(image))
enc = _kv_read_checked(settings, 'encryption', default={})
enc_cert = _kv_read_checked(enc, 'certificate', default={})
singularity_images_settings.append(
SingularityImageSettings(
image=image, image=image,
key_fingerprint=key_fingerprint, key_fingerprint=key_fingerprint,
key_file=key_file_path, key_file=key_file_path,
encryption_certificate_sha1_thumbprint=_kv_read_checked(
enc_cert, 'sha1_thumbprint'),
) )
) )
return singularity_signed_images_settings return singularity_images_settings
def singularity_signed_images_key_fingerprint_dict(config): def singularity_signed_images_key_fingerprint_dict(config):
@ -2463,10 +2476,31 @@ def singularity_signed_images_key_fingerprint_dict(config):
:rtype: dict :rtype: dict
:return: singularity signed images to key fingerprint :return: singularity signed images to key fingerprint
""" """
images_settings = global_resources_singularity_images_settings(
config, True)
return dict(
(settings.image, settings.key_fingerprint)
for settings in images_settings
)
def singularity_image_to_encryption_cert_map(config):
# type: (dict) -> dict
"""Get mapping of image to encryptiong cert thumbprint
:param dict config: configuration object
:rtype: dict
:return: singularity image name to cert thumbprint
"""
images_settings = ( images_settings = (
global_resources_singularity_signed_images_settings(config)) global_resources_singularity_images_settings(config, False) +
return dict((settings.image, settings.key_fingerprint) global_resources_singularity_images_settings(config, True)
for settings in images_settings) )
image_map = {}
for image in images_settings:
if util.is_not_empty(image.encryption_certificate_sha1_thumbprint):
image_map[
image.image] = image.encryption_certificate_sha1_thumbprint
return image_map
def global_resources_files(config): def global_resources_files(config):
@ -4041,6 +4075,13 @@ def task_settings(
if username is not None and password is not None: if username is not None and password is not None:
env_vars['SINGULARITY_DOCKER_USERNAME'] = username env_vars['SINGULARITY_DOCKER_USERNAME'] = username
env_vars['SINGULARITY_DOCKER_PASSWORD'] = password env_vars['SINGULARITY_DOCKER_PASSWORD'] = password
singularity_cert_map = singularity_image_to_encryption_cert_map(config)
cert = singularity_cert_map.get(singularity_image)
if cert is not None:
# use run option over env var to use az batch env var to cert path
run_opts.append(
'--pem-path=$AZ_BATCH_NODE_STARTUP_DIR/certs/'
'sha1-{}-rsa.pem'.format(cert))
# constraints # constraints
max_task_retries = _kv_read(conf, 'max_task_retries') max_task_retries = _kv_read(conf, 'max_task_retries')
max_wall_time = _kv_read_checked(conf, 'max_wall_time') max_wall_time = _kv_read_checked(conf, 'max_wall_time')

Просмотреть файл

@ -77,7 +77,21 @@ mapping:
unsigned: unsigned:
type: seq type: seq
sequence: sequence:
- type: str - type: map
mapping:
image:
type: str
required: true
encryption:
type: map
mapping:
certificate:
type: map
required: true
mapping:
sha1_thumbprint:
type: str
required: true
signed: signed:
type: seq type: seq
sequence: sequence:
@ -86,11 +100,24 @@ mapping:
image: image:
type: str type: str
required: true required: true
key_fingerprint: signing_key:
type: str type: map
required: true mapping:
key_file: fingerprint:
type: str type: str
required: true
file:
type: str
encryption:
type: map
mapping:
certificate:
type: map
required: true
mapping:
sha1_thumbprint:
type: str
required: true
volumes: volumes:
type: map type: map
mapping: mapping:

Просмотреть файл

@ -53,10 +53,10 @@ docker_login() {
# decrypt passwords if necessary # decrypt passwords if necessary
if [ "$1" == "-e" ]; then if [ "$1" == "-e" ]; then
if [ -n "$DOCKER_LOGIN_PASSWORD" ]; then if [ -n "$DOCKER_LOGIN_PASSWORD" ]; then
DOCKER_LOGIN_PASSWORD=$(echo "$DOCKER_LOGIN_PASSWORD" | base64 -d | openssl rsautl -decrypt -inkey ../certs/key.pem) DOCKER_LOGIN_PASSWORD=$(echo "$DOCKER_LOGIN_PASSWORD" | base64 -d | openssl rsautl -decrypt -inkey ../certs/shipyard-enckey.pem)
fi fi
if [ -n "$SINGULARITY_LOGIN_PASSWORD" ]; then if [ -n "$SINGULARITY_LOGIN_PASSWORD" ]; then
SINGULARITY_LOGIN_PASSWORD=$(echo "$SINGULARITY_LOGIN_PASSWORD" | base64 -d | openssl rsautl -decrypt -inkey ../certs/key.pem) SINGULARITY_LOGIN_PASSWORD=$(echo "$SINGULARITY_LOGIN_PASSWORD" | base64 -d | openssl rsautl -decrypt -inkey ../certs/shipyard-enckey.pem)
fi fi
fi fi

Просмотреть файл

@ -18,7 +18,7 @@ for spec in "$@"; do
eo=${parts[5]} eo=${parts[5]}
cond=${parts[6]} cond=${parts[6]}
# decrypt ciphertext # decrypt ciphertext
privatekey=$AZ_BATCH_NODE_STARTUP_DIR/certs/key.pem privatekey=$AZ_BATCH_NODE_STARTUP_DIR/certs/shipyard-enckey.pem
cipher=$(echo "$cipher" | base64 -d | openssl rsautl -decrypt -inkey "$privatekey") cipher=$(echo "$cipher" | base64 -d | openssl rsautl -decrypt -inkey "$privatekey")
IFS=',' read -ra storage <<< "$cipher" IFS=',' read -ra storage <<< "$cipher"
sa=${storage[0]} sa=${storage[0]}

0
scripts/shipyard_cascade.sh Normal file → Executable file
Просмотреть файл

Просмотреть файл

@ -187,7 +187,9 @@ while getopts "h?abcde:fg:i:jkl:m:no:p:qrs:tuv:wx:yz:" opt; do
lis=$OPTARG lis=$OPTARG
;; ;;
m) m)
OLD_IFS=$IFS
IFS=',' read -ra sc_args <<< "${OPTARG,,}" IFS=',' read -ra sc_args <<< "${OPTARG,,}"
IFS=$OLD_IFS
;; ;;
n) n)
native_mode=1 native_mode=1
@ -726,7 +728,9 @@ install_nvidia_software() {
# check for nvidia card # check for nvidia card
check_for_nvidia_card check_for_nvidia_card
# split arg into two # split arg into two
local OLD_IFS=$IFS
IFS=':' read -ra GPUARGS <<< "$gpu" IFS=':' read -ra GPUARGS <<< "$gpu"
IFS=$OLD_IFS
local is_viz=${GPUARGS[0]} local is_viz=${GPUARGS[0]}
local nvdriver=${GPUARGS[1]} local nvdriver=${GPUARGS[1]}
# remove nouveau # remove nouveau
@ -1090,7 +1094,9 @@ install_kata_containers() {
process_fstab_entry() { process_fstab_entry() {
local desc=$1 local desc=$1
local fstab_entry=$2 local fstab_entry=$2
local OLD_IFS=$IFS
IFS=' ' read -ra fs <<< "$fstab_entry" IFS=' ' read -ra fs <<< "$fstab_entry"
IFS=$OLD_IFS
local mountpoint="${fs[1]}" local mountpoint="${fs[1]}"
log INFO "Creating host directory for $desc at $mountpoint" log INFO "Creating host directory for $desc at $mountpoint"
mkdir -p "$mountpoint" mkdir -p "$mountpoint"
@ -1126,6 +1132,7 @@ mount_storage_clusters() {
if [ -n "$SHIPYARD_STORAGE_CLUSTER_FSTAB" ]; then if [ -n "$SHIPYARD_STORAGE_CLUSTER_FSTAB" ]; then
log DEBUG "Mounting storage clusters" log DEBUG "Mounting storage clusters"
local fstab_mounts local fstab_mounts
local OLD_IFS=$IFS
IFS='#' read -ra fstab_mounts <<< "$SHIPYARD_STORAGE_CLUSTER_FSTAB" IFS='#' read -ra fstab_mounts <<< "$SHIPYARD_STORAGE_CLUSTER_FSTAB"
for fstab in "${fstab_mounts[@]}"; do for fstab in "${fstab_mounts[@]}"; do
# eval and split fstab var to expand vars # eval and split fstab var to expand vars
@ -1133,6 +1140,7 @@ mount_storage_clusters() {
IFS=' ' read -ra parts <<< "$fstab_entry" IFS=' ' read -ra parts <<< "$fstab_entry"
mount "${parts[1]}" mount "${parts[1]}"
done done
IFS=$OLD_IFS
log INFO "Storage clusters mounted" log INFO "Storage clusters mounted"
fi fi
} }
@ -1142,6 +1150,7 @@ process_storage_clusters() {
log DEBUG "Processing storage clusters" log DEBUG "Processing storage clusters"
# eval and split fstab var to expand vars (this is ok since it is set by shipyard) # eval and split fstab var to expand vars (this is ok since it is set by shipyard)
local fstab_mounts local fstab_mounts
local OLD_IFS=$IFS
fstab_mounts=$(eval echo "$SHIPYARD_STORAGE_CLUSTER_FSTAB") fstab_mounts=$(eval echo "$SHIPYARD_STORAGE_CLUSTER_FSTAB")
IFS='#' read -ra fstabs <<< "$fstab_mounts" IFS='#' read -ra fstabs <<< "$fstab_mounts"
i=0 i=0
@ -1151,6 +1160,7 @@ process_storage_clusters() {
process_fstab_entry "$sc_arg" "$fstab_entry" process_fstab_entry "$sc_arg" "$fstab_entry"
i=$((i + 1)) i=$((i + 1))
done done
IFS=$OLD_IFS
log INFO "Storage clusters processed" log INFO "Storage clusters processed"
fi fi
} }
@ -1159,6 +1169,7 @@ mount_custom_fstab() {
if [ -n "$SHIPYARD_CUSTOM_MOUNTS_FSTAB" ]; then if [ -n "$SHIPYARD_CUSTOM_MOUNTS_FSTAB" ]; then
log DEBUG "Mounting custom mounts via fstab" log DEBUG "Mounting custom mounts via fstab"
local fstab_mounts local fstab_mounts
local OLD_IFS=$IFS
IFS='#' read -ra fstab_mounts <<< "$SHIPYARD_CUSTOM_MOUNTS_FSTAB" IFS='#' read -ra fstab_mounts <<< "$SHIPYARD_CUSTOM_MOUNTS_FSTAB"
for fstab in "${fstab_mounts[@]}"; do for fstab in "${fstab_mounts[@]}"; do
# eval and split fstab var to expand vars # eval and split fstab var to expand vars
@ -1166,6 +1177,7 @@ mount_custom_fstab() {
IFS=' ' read -ra parts <<< "$fstab_entry" IFS=' ' read -ra parts <<< "$fstab_entry"
mount "${parts[1]}" mount "${parts[1]}"
done done
IFS=$OLD_IFS
log INFO "Custom mounts via fstab mounted" log INFO "Custom mounts via fstab mounted"
fi fi
} }
@ -1174,6 +1186,7 @@ process_custom_fstab() {
if [ -n "$SHIPYARD_CUSTOM_MOUNTS_FSTAB" ]; then if [ -n "$SHIPYARD_CUSTOM_MOUNTS_FSTAB" ]; then
log DEBUG "Processing custom mounts via fstab" log DEBUG "Processing custom mounts via fstab"
local fstab_mounts local fstab_mounts
local OLD_IFS=$IFS
IFS='#' read -ra fstab_mounts <<< "$SHIPYARD_CUSTOM_MOUNTS_FSTAB" IFS='#' read -ra fstab_mounts <<< "$SHIPYARD_CUSTOM_MOUNTS_FSTAB"
for fstab in "${fstab_mounts[@]}"; do for fstab in "${fstab_mounts[@]}"; do
# eval and split fstab var to expand vars # eval and split fstab var to expand vars
@ -1181,17 +1194,49 @@ process_custom_fstab() {
IFS=' ' read -ra parts <<< "$fstab_entry" IFS=' ' read -ra parts <<< "$fstab_entry"
process_fstab_entry "${parts[2]}" "$fstab_entry" process_fstab_entry "${parts[2]}" "$fstab_entry"
done done
IFS=$OLD_IFS
log INFO "Custom mounts via fstab processed" log INFO "Custom mounts via fstab processed"
fi fi
} }
convert_singularity_certificates() {
# converts pfx certs to PKCS1 (RSA private) pem certs for singularity
if [ -z "$SHIPYARD_SINGULARITY_DECRYPTION_CERTIFICATES" ]; then
log INFO "No singularity decryption certificates defined"
return
fi
log INFO "Processing Singularity decryption certificates: $SHIPYARD_SINGULARITY_DECRYPTION_CERTIFICATES"
local OLD_IFS=$IFS
IFS=',' read -ra cert_tps <<< "${SHIPYARD_SINGULARITY_DECRYPTION_CERTIFICATES,,}"
IFS=$OLD_IFS
local pfx
local pw
local pkcs1
pushd "$AZ_BATCH_CERTIFICATES_DIR"
for tp in "${cert_tps[@]}"; do
pfx="sha1-${tp}.pfx"
pw="${pfx}.pw"
if [ ! -f "$pfx" ] || [ ! -f "$pw" ]; then
log ERROR "Certificate needed for Singularity image decryption not found: $pfx or $pw"
exit 1
fi
pkcs1="sha1-${tp}-rsa.pem"
openssl pkcs12 -in "$pfx" -nodes -password "file:${pw}" | openssl rsa -out "$pkcs1"
chmod 640 "$pkcs1"
chown _azbatch:_azbatchsudogrp "$pkcs1"
# remove the pfx/pw files bound to start task
rm -f "$pfx" "$pw"
done
popd
}
decrypt_encrypted_credentials() { decrypt_encrypted_credentials() {
# convert pfx to pem # convert pfx to pem
pfxfile=$AZ_BATCH_CERTIFICATES_DIR/sha1-$encrypted.pfx pfxfile=$AZ_BATCH_CERTIFICATES_DIR/sha1-$encrypted.pfx
privatekey=$AZ_BATCH_CERTIFICATES_DIR/key.pem privatekey=$AZ_BATCH_CERTIFICATES_DIR/shipyard-enckey.pem
openssl pkcs12 -in "$pfxfile" -out "$privatekey" -nodes -password file:"${pfxfile}".pw openssl pkcs12 -in "$pfxfile" -out "$privatekey" -nodes -password "file:${pfxfile}.pw"
# remove pfx-related files # remove pfx-related files
rm -f "$pfxfile" "${pfxfile}".pw rm -f "$pfxfile" "${pfxfile}.pw"
# decrypt creds # decrypt creds
SHIPYARD_STORAGE_ENV=$(echo "$SHIPYARD_STORAGE_ENV" | base64 -d | openssl rsautl -decrypt -inkey "$privatekey") SHIPYARD_STORAGE_ENV=$(echo "$SHIPYARD_STORAGE_ENV" | base64 -d | openssl rsautl -decrypt -inkey "$privatekey")
if [[ -n ${DOCKER_LOGIN_USERNAME+x} ]]; then if [[ -n ${DOCKER_LOGIN_USERNAME+x} ]]; then
@ -1372,7 +1417,9 @@ check_for_storage_cluster_software() {
fi fi
local rc local rc
for sc_arg in "${sc_args[@]}"; do for sc_arg in "${sc_args[@]}"; do
local OLD_IFS=$IFS
IFS=':' read -ra sc <<< "$sc_arg" IFS=':' read -ra sc <<< "$sc_arg"
IFS=$OLD_IFS
local server_type=${sc[0]} local server_type=${sc[0]}
if [ "$server_type" == "nfs" ]; then if [ "$server_type" == "nfs" ]; then
set +e set +e
@ -1407,7 +1454,9 @@ install_storage_cluster_dependencies() {
local repo="http://download.opensuse.org/repositories/filesystems/${repodir}/filesystems.repo" local repo="http://download.opensuse.org/repositories/filesystems/${repodir}/filesystems.repo"
fi fi
for sc_arg in "${sc_args[@]}"; do for sc_arg in "${sc_args[@]}"; do
local OLD_IFS=$IFS
IFS=':' read -ra sc <<< "$sc_arg" IFS=':' read -ra sc <<< "$sc_arg"
IFS=$OLD_IFS
server_type=${sc[0]} server_type=${sc[0]}
if [ "$server_type" == "nfs" ]; then if [ "$server_type" == "nfs" ]; then
if [ "$PACKAGER" == "apt" ]; then if [ "$PACKAGER" == "apt" ]; then
@ -1677,6 +1726,7 @@ install_and_start_node_exporter() {
else else
ib="--no-collector.infiniband" ib="--no-collector.infiniband"
fi fi
local OLD_IFS=$IFS
if [ -n "${sc_args[0]}" ]; then if [ -n "${sc_args[0]}" ]; then
for sc_arg in "${sc_args[@]}"; do for sc_arg in "${sc_args[@]}"; do
IFS=':' read -ra sc <<< "$sc_arg" IFS=':' read -ra sc <<< "$sc_arg"
@ -1685,9 +1735,12 @@ install_and_start_node_exporter() {
break break
fi fi
done done
IFS=$OLD_IFS
fi fi
local pneo local pneo
OLD_IFS=$IFS
IFS=',' read -ra pneo <<< "$PROM_NODE_EXPORTER_OPTIONS" IFS=',' read -ra pneo <<< "$PROM_NODE_EXPORTER_OPTIONS"
IFS=$OLD_IFS
# shellcheck disable=SC2086 # shellcheck disable=SC2086
"${AZ_BATCH_TASK_WORKING_DIR}"/node_exporter \ "${AZ_BATCH_TASK_WORKING_DIR}"/node_exporter \
"$ib" $nfs \ "$ib" $nfs \
@ -1715,7 +1768,9 @@ install_and_start_cadvisor() {
# start # start
local pcao local pcao
if [ -n "${PROM_CADVISOR_OPTIONS}" ]; then if [ -n "${PROM_CADVISOR_OPTIONS}" ]; then
local OLD_IFS=$IFS
IFS=',' read -ra pcao <<< "$PROM_CADVISOR_OPTIONS" IFS=',' read -ra pcao <<< "$PROM_CADVISOR_OPTIONS"
IFS=$OLD_IFS
else else
pcao=() pcao=()
fi fi
@ -1769,6 +1824,7 @@ echo "Docker image preload delay: $delay_preload"
echo "Cascade via container: $cascadecontainer" echo "Cascade via container: $cascadecontainer"
echo "Concurrent source downloads: $concurrent_source_downloads" echo "Concurrent source downloads: $concurrent_source_downloads"
echo "Block on images: $block" echo "Block on images: $block"
echo "Singularity decryption certs: $SHIPYARD_SINGULARITY_DECRYPTION_CERTIFICATES"
echo "" echo ""
# set python env vars # set python env vars
@ -1804,6 +1860,9 @@ if [ -n "$encrypted" ]; then
decrypt_encrypted_credentials decrypt_encrypted_credentials
fi fi
# convert certs to pkcs1 for singularity encrypted container support
convert_singularity_certificates
# create shared mount points # create shared mount points
mkdir -p "$MOUNTS_PATH" mkdir -p "$MOUNTS_PATH"