feat(secrets): Add support for secrets

Adds support for -
- (1) Declared secrets with the file location.
- (2) Declared secrets with file location, mounted as a different named secret.
- (3) Declared secrets with file location, mounted at arbitrary location.
- (4) External secrets (type=mount), mounted as original secret name.
- (5) External secrets (type=mount), mounted as original secret name, with specified uid, gid and mode.
This commit is contained in:
Sam 2021-07-21 09:22:07 -07:00 committed by Muayyad Alsadi
parent 3836094c64
commit 62aa337f17
6 changed files with 157 additions and 1 deletions

View File

@ -513,6 +513,74 @@ def get_mount_args(compose, cnt, volume):
args = mount_desc_to_mount_args(compose, volume, srv_name, cnt['name'])
return ['--mount', args]
def get_secret_args(compose, cnt, secret):
secret_name = secret if is_str(secret) else secret.get('source', None)
if not secret_name or secret_name not in compose.declared_secrets.keys():
raise ValueError(
'ERROR: undeclared secret: "{}", service: "{}"'
.format(secret, cnt['_service'])
)
declared_secret = compose.declared_secrets[secret_name]
source_file = declared_secret.get('file', None)
dest_file = ''
secret_opts = ''
target = None if is_str(secret) else secret.get('target', None)
uid = None if is_str(secret) else secret.get('uid', None)
gid = None if is_str(secret) else secret.get('gid', None)
mode = None if is_str(secret) else secret.get('mode', None)
if source_file:
if not target:
dest_file = '/run/secrets/{}'.format(secret_name)
elif not target.startswith("/"):
dest_file = '/run/secrets/{}'.format(target if target else secret_name)
else:
dest_file = target
volume_ref = [
'--volume', '{}:{}:ro,rprivate,rbind'.format(source_file, dest_file)
]
if uid or gid or mode:
print(
'WARNING: Service "{}" uses secret "{}" with uid, gid, or mode.'
.format(cnt['_service'], target if target else secret_name)
+ ' These fields are not supported by this implementation of the Compose file'
)
return volume_ref
# v3.5 and up added external flag, earlier the spec
# only required a name to be specified.
# docker-compose does not support external secrets outside of swarm mode.
# However accessing these via podman is trivial
# since these commands are directly translated to
# podman-create commands, albiet we can only support a 1:1 mapping
# at the moment
if declared_secret.get('external', False) or declared_secret.get('name', None):
secret_opts += ',uid={}'.format(uid) if uid else ''
secret_opts += ',gid={}'.format(gid) if gid else ''
secret_opts += ',mode={}'.format(mode) if mode else ''
# The target option is only valid for type=env,
# which in an ideal world would work
# for type=mount as well.
# having a custom name for the external secret
# has the same problem as well
ext_name = declared_secret.get('name', None)
err_str = 'ERROR: Custom name/target reference "{}" for mounted external secret "{}" is not supported'
if ext_name and ext_name != secret_name:
raise ValueError(err_str.format(secret_name, ext_name))
elif target and target != secret_name:
raise ValueError(err_str.format(target, secret_name))
elif target:
print('WARNING: Service "{}" uses target: "{}" for secret: "{}".'
.format(cnt['_service'], target, secret_name)
+ ' That is un-supported and a no-op and is ignored.')
return [ '--secret', '{}{}'.format(secret_name, secret_opts) ]
raise ValueError('ERROR: unparseable secret: "{}", service: "{}"'
.format(secret_name, cnt['_service']))
def container_to_res_args(cnt, podman_args):
# v2 < https://docs.docker.com/compose/compose-file/compose-file-v2/#cpu-and-other-resources
cpus_limit_v2 = try_float(cnt.get('cpus', None), None)
@ -587,6 +655,8 @@ def container_to_args(compose, cnt, detached=True):
for volume in cnt.get('volumes', []):
# TODO: should we make it os.path.realpath(os.path.join(, i))?
podman_args.extend(get_mount_args(compose, cnt, volume))
for secret in cnt.get('secrets', []):
podman_args.extend(get_secret_args(compose, cnt, secret))
for i in cnt.get('extra_hosts', []):
podman_args.extend(['--add-host', i])
for i in cnt.get('expose', []):
@ -850,6 +920,7 @@ class PodmanCompose:
self.pods = None
self.containers = None
self.shared_vols = None
self.declared_secrets = None
self.container_names_by_service = None
self.container_by_name = None
self._prefer_volume_over_mount = True
@ -1006,7 +1077,7 @@ class PodmanCompose:
# other top-levels:
# networks: {driver: ...}
# configs: {...}
# secrets: {...}
self.declared_secrets = compose.get('secrets', {})
given_containers = []
container_names_by_service = {}
for service_name, service_desc in services.items():

View File

@ -0,0 +1,18 @@
version: "3.8"
services:
test:
image: busybox
command:
- cat
- /run/secrets/new_secret
tmpfs:
- /run
- /tmp
secrets:
- new_secret
secrets:
new_secret:
external: true
name: my_secret

View File

@ -0,0 +1,18 @@
version: "3.8"
services:
test:
image: busybox
command:
- cat
- /run/secrets/my_secret_2
tmpfs:
- /run
- /tmp
secrets:
- source: my_secret
target: new_secret
secrets:
my_secret:
external: true

View File

@ -0,0 +1,42 @@
version: "3.8"
services:
test:
image: busybox
command:
- /tmp/print_secrets.sh
tmpfs:
- /run
- /tmp
volumes:
- ./print_secrets.sh:/tmp/print_secrets.sh
secrets:
- my_secret
- my_secret_2
- source: my_secret_3
target: my_secret_3
uid: '103'
gid: '103'
mode: 400
- file_secret
- source: file_secret
target: custom_name
- source: file_secret
target: /etc/custom_location
- source: file_secret
target: unused_params_warning
uid: '103'
gid: '103'
mode: 400
secrets:
my_secret:
external: true
my_secret_2:
external: true
name: my_secret_2
my_secret_3:
external: true
name: my_secret_3
file_secret:
file: ./my_secret

1
tests/secrets/my_secret Normal file
View File

@ -0,0 +1 @@
important-secret-is-important

6
tests/secrets/print_secrets.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
ls -la /run/secrets/*
ls -la /etc/custom_location
cat /run/secrets/*
cat /etc/custom_location