Add a restricted mode (read authentication required)
Enable running the registry in a mode where authentication is required for pulling images. This could be useful in an environment where even an intermediate or buildset registry should require authentication to pull images. Or it could make this more useful as a general registry (that's not a priority use case for this project, but this doesn't add much complexity). If a "read" level user is specified, then we assume that anonymous read access should not be allowed. Change-Id: I1455a1031590ff0206a4b6da0d8c08093cf0e3cd
This commit is contained in:
parent
bf98edd796
commit
b635c65cf3
70
playbooks/functional-test/restricted.yaml
Normal file
70
playbooks/functional-test/restricted.yaml
Normal file
@ -0,0 +1,70 @@
|
||||
# Test push and pull from the registry in restricted mode (read access
|
||||
# restricted)
|
||||
|
||||
- name: Start the registry
|
||||
shell:
|
||||
cmd: docker-compose up -d
|
||||
chdir: "{{ ansible_user_dir }}/src/opendev.org/zuul/zuul-registry/playbooks/functional-test/restricted"
|
||||
|
||||
- name: Print list of images
|
||||
command: docker image ls --all --digests --no-trunc
|
||||
register: image_list
|
||||
failed_when: "'test/image' in image_list.stdout"
|
||||
|
||||
- name: Copy the test image into local docker image storage
|
||||
command: >
|
||||
skopeo copy
|
||||
docker-archive:{{ workspace }}/test.img
|
||||
docker-daemon:localhost:9000/test/image:latest
|
||||
|
||||
- name: Log in to registry
|
||||
command: docker login localhost:9000 -u writeuser -p writepass
|
||||
|
||||
- name: Push the test image to the registry
|
||||
command: docker push localhost:9000/test/image
|
||||
|
||||
- name: Remove the test image from the local cache
|
||||
command: docker rmi localhost:9000/test/image
|
||||
|
||||
- name: Log out of registry
|
||||
command: docker logout localhost:9000
|
||||
|
||||
- name: Try to pull the image from the registry unauthenticated
|
||||
command: docker pull localhost:9000/test/image
|
||||
register: result
|
||||
failed_when: result.rc != 1
|
||||
|
||||
- name: Log in to registry
|
||||
command: docker login localhost:9000 -u readuser -p readpass
|
||||
|
||||
- name: Print list of images
|
||||
command: docker image ls --all --digests --no-trunc
|
||||
register: image_list
|
||||
failed_when: "'test/image' in image_list.stdout"
|
||||
|
||||
- name: Pull the image from the registry
|
||||
command: docker pull localhost:9000/test/image
|
||||
|
||||
- name: Print list of images
|
||||
command: docker image ls --all --digests --no-trunc
|
||||
register: image_list
|
||||
failed_when: "'test/image' not in image_list.stdout"
|
||||
|
||||
- name: Try to pull an image that does not exist
|
||||
command: docker pull localhost:9000/test/dne
|
||||
register: result
|
||||
failed_when: result.rc != 1
|
||||
|
||||
- name: Remove the test image from the local cache
|
||||
command: docker rmi localhost:9000/test/image
|
||||
|
||||
- name: Stop the registry
|
||||
shell:
|
||||
cmd: docker-compose down
|
||||
chdir: "{{ ansible_user_dir }}/src/opendev.org/zuul/zuul-registry/playbooks/functional-test/restricted"
|
||||
|
||||
- name: Clean up docker volumes
|
||||
command: docker volume prune -f
|
||||
|
||||
- name: Log out of registry
|
||||
command: docker logout localhost:9000
|
17
playbooks/functional-test/restricted/conf/registry.yaml
Normal file
17
playbooks/functional-test/restricted/conf/registry.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
registry:
|
||||
address: '0.0.0.0'
|
||||
port: 9000
|
||||
public-url: https://localhost:9000
|
||||
tls-cert: /tls/cert.pem
|
||||
tls-key: /tls/cert.key
|
||||
secret: test_token_secret
|
||||
users:
|
||||
- name: writeuser
|
||||
pass: writepass
|
||||
access: write
|
||||
- name: readuser
|
||||
pass: readpass
|
||||
access: read
|
||||
storage:
|
||||
driver: filesystem
|
||||
root: /storage
|
12
playbooks/functional-test/restricted/docker-compose.yaml
Normal file
12
playbooks/functional-test/restricted/docker-compose.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
# Version 2 is the latest that is supported by docker-compose in
|
||||
# Ubuntu Xenial.
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
registry:
|
||||
image: zuul/zuul-registry
|
||||
volumes:
|
||||
- "./conf/:/conf/:z"
|
||||
- "/tmp/registry-test/tls/:/tls:z"
|
||||
ports:
|
||||
- "9000:9000"
|
@ -35,6 +35,12 @@
|
||||
- name: Run docker test tasks
|
||||
include_tasks: docker.yaml
|
||||
|
||||
- hosts: all
|
||||
name: Run restricted buildset registry test
|
||||
tasks:
|
||||
- name: Run restricted buildset test tasks
|
||||
include_tasks: restricted.yaml
|
||||
|
||||
- hosts: all
|
||||
name: Run podman standard registry test
|
||||
tasks:
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2019 Red Hat, Inc.
|
||||
# Copyright 2021 Acme Gating, LLC
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -47,11 +48,20 @@ class Authorization(cherrypy.Tool):
|
||||
self.secret = secret
|
||||
self.public_url = public_url
|
||||
self.rw = {}
|
||||
self.ro = {}
|
||||
self.anonymous_read = True
|
||||
|
||||
for user in users:
|
||||
if user['access'] == self.WRITE:
|
||||
self.rw[user['name']] = user['pass']
|
||||
if user['access'] == self.READ:
|
||||
self.ro[user['name']] = user['pass']
|
||||
self.anonymous_read = False
|
||||
|
||||
if self.anonymous_read:
|
||||
self.log.info("Anonymous read access enabled")
|
||||
else:
|
||||
self.log.info("Anonymous read access disabled")
|
||||
cherrypy.Tool.__init__(self, 'before_handler',
|
||||
self.check_auth,
|
||||
priority=1)
|
||||
@ -90,10 +100,13 @@ class Authorization(cherrypy.Tool):
|
||||
and level is None):
|
||||
level = self.READ
|
||||
if level is None:
|
||||
# No scope was provided, so this is an authentication
|
||||
# request; treat it as requesting 'write' access so that
|
||||
# we validate the password.
|
||||
level = self.WRITE
|
||||
if self.anonymous_read:
|
||||
# No scope was provided, so this is an authentication
|
||||
# request; treat it as requesting 'write' access so
|
||||
# that we validate the password.
|
||||
level = self.WRITE
|
||||
else:
|
||||
level = self.READ
|
||||
return level
|
||||
|
||||
@cherrypy.expose
|
||||
@ -119,19 +132,30 @@ class Authorization(cherrypy.Tool):
|
||||
level = self._get_level(kw.get('scope', ''))
|
||||
self.log.info('Authenticate level %s', level)
|
||||
if level == self.WRITE:
|
||||
if auth_header and 'Basic' in auth_header:
|
||||
cred = auth_header.split()[1]
|
||||
cred = base64.decodebytes(cred.encode('utf8')).decode('utf8')
|
||||
user, pw = cred.split(':', 1)
|
||||
if not self.check(self.rw, user, pw):
|
||||
self.unauthorized()
|
||||
else:
|
||||
self.unauthorized()
|
||||
self._check_creds(auth_header, [self.rw])
|
||||
elif level == self.READ and not self.anonymous_read:
|
||||
self._check_creds(auth_header, [self.rw, self.ro])
|
||||
# If we permit anonymous read and we're requesting read, no
|
||||
# check is performed.
|
||||
self.log.debug('Generate %s token', level)
|
||||
token = jwt.encode({'level': level}, 'secret', algorithm='HS256')
|
||||
return {'token': token,
|
||||
'access_token': token}
|
||||
|
||||
def _check_creds(self, auth_header, credstores):
|
||||
# If the password is okay, fall through; otherwise call
|
||||
# unauthorized for the side effect of raising an exception.
|
||||
if auth_header and 'Basic' in auth_header:
|
||||
cred = auth_header.split()[1]
|
||||
cred = base64.decodebytes(cred.encode('utf8')).decode('utf8')
|
||||
user, pw = cred.split(':', 1)
|
||||
# Return true on the first credstore with the user, false otherwise
|
||||
if not next(filter(
|
||||
lambda cs: self.check(cs, user, pw), credstores), False):
|
||||
self.unauthorized()
|
||||
else:
|
||||
self.unauthorized()
|
||||
|
||||
|
||||
class RegistryAPI:
|
||||
"""Registry API server.
|
||||
|
Loading…
x
Reference in New Issue
Block a user