Start of a v2 API.
This commit is contained in:
parent
3bfa2a6509
commit
acbcc2e206
16 changed files with 508 additions and 55 deletions
29
endpoints/v1/__init__.py
Normal file
29
endpoints/v1/__init__.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from flask import Blueprint, make_response
|
||||
|
||||
from endpoints.decorators import anon_protect, anon_allowed
|
||||
|
||||
|
||||
v1_bp = Blueprint('v1', __name__)
|
||||
|
||||
|
||||
# Note: This is *not* part of the Docker index spec. This is here for our own health check,
|
||||
# since we have nginx handle the _ping below.
|
||||
@v1_bp.route('/_internal_ping')
|
||||
@anon_allowed
|
||||
def internal_ping():
|
||||
return make_response('true', 200)
|
||||
|
||||
|
||||
@v1_bp.route('/_ping')
|
||||
@anon_allowed
|
||||
def ping():
|
||||
# NOTE: any changes made here must also be reflected in the nginx config
|
||||
response = make_response('true', 200)
|
||||
response.headers['X-Docker-Registry-Version'] = '0.6.0'
|
||||
response.headers['X-Docker-Registry-Standalone'] = '0'
|
||||
return response
|
||||
|
||||
|
||||
from endpoints.v1 import index
|
||||
from endpoints.v1 import registry
|
||||
from endpoints.v1 import tags
|
|
@ -18,15 +18,15 @@ from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission,
|
|||
AlwaysFailPermission, repository_read_grant, repository_write_grant)
|
||||
|
||||
from util.http import abort
|
||||
from endpoints.v1 import v1_bp
|
||||
from endpoints.trackhelper import track_and_log
|
||||
from endpoints.notificationhelper import spawn_notification
|
||||
from endpoints.decorators import anon_protect, anon_allowed
|
||||
|
||||
import features
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
index = Blueprint('index', __name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GrantType(object):
|
||||
|
@ -73,8 +73,8 @@ def generate_headers(scope=GrantType.READ_REPOSITORY, add_grant_for_status=None)
|
|||
return decorator_method
|
||||
|
||||
|
||||
@index.route('/users', methods=['POST'])
|
||||
@index.route('/users/', methods=['POST'])
|
||||
@v1_bp.route('/users', methods=['POST'])
|
||||
@v1_bp.route('/users/', methods=['POST'])
|
||||
@anon_allowed
|
||||
def create_user():
|
||||
user_data = request.get_json()
|
||||
|
@ -123,8 +123,8 @@ def create_user():
|
|||
abort(400, error_message, issue='login-failure')
|
||||
|
||||
|
||||
@index.route('/users', methods=['GET'])
|
||||
@index.route('/users/', methods=['GET'])
|
||||
@v1_bp.route('/users', methods=['GET'])
|
||||
@v1_bp.route('/users/', methods=['GET'])
|
||||
@process_auth
|
||||
@anon_allowed
|
||||
def get_user():
|
||||
|
@ -146,7 +146,7 @@ def get_user():
|
|||
abort(404)
|
||||
|
||||
|
||||
@index.route('/users/<username>/', methods=['PUT'])
|
||||
@v1_bp.route('/users/<username>/', methods=['PUT'])
|
||||
@process_auth
|
||||
@anon_allowed
|
||||
def update_user(username):
|
||||
|
@ -172,7 +172,7 @@ def update_user(username):
|
|||
abort(403)
|
||||
|
||||
|
||||
@index.route('/repositories/<path:repository>', methods=['PUT'])
|
||||
@v1_bp.route('/repositories/<path:repository>', methods=['PUT'])
|
||||
@process_auth
|
||||
@parse_repository_name
|
||||
@generate_headers(scope=GrantType.WRITE_REPOSITORY, add_grant_for_status=201)
|
||||
|
@ -227,7 +227,7 @@ def create_repository(namespace, repository):
|
|||
return make_response('Created', 201)
|
||||
|
||||
|
||||
@index.route('/repositories/<path:repository>/images', methods=['PUT'])
|
||||
@v1_bp.route('/repositories/<path:repository>/images', methods=['PUT'])
|
||||
@process_auth
|
||||
@parse_repository_name
|
||||
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
|
||||
|
@ -260,7 +260,7 @@ def update_images(namespace, repository):
|
|||
abort(403)
|
||||
|
||||
|
||||
@index.route('/repositories/<path:repository>/images', methods=['GET'])
|
||||
@v1_bp.route('/repositories/<path:repository>/images', methods=['GET'])
|
||||
@process_auth
|
||||
@parse_repository_name
|
||||
@generate_headers(scope=GrantType.READ_REPOSITORY)
|
||||
|
@ -286,7 +286,7 @@ def get_repository_images(namespace, repository):
|
|||
abort(403)
|
||||
|
||||
|
||||
@index.route('/repositories/<path:repository>/images', methods=['DELETE'])
|
||||
@v1_bp.route('/repositories/<path:repository>/images', methods=['DELETE'])
|
||||
@process_auth
|
||||
@parse_repository_name
|
||||
@generate_headers(scope=GrantType.WRITE_REPOSITORY)
|
||||
|
@ -295,14 +295,14 @@ def delete_repository_images(namespace, repository):
|
|||
abort(501, 'Not Implemented', issue='not-implemented')
|
||||
|
||||
|
||||
@index.route('/repositories/<path:repository>/auth', methods=['PUT'])
|
||||
@v1_bp.route('/repositories/<path:repository>/auth', methods=['PUT'])
|
||||
@parse_repository_name
|
||||
@anon_allowed
|
||||
def put_repository_auth(namespace, repository):
|
||||
abort(501, 'Not Implemented', issue='not-implemented')
|
||||
|
||||
|
||||
@index.route('/search', methods=['GET'])
|
||||
@v1_bp.route('/search', methods=['GET'])
|
||||
@process_auth
|
||||
@anon_protect
|
||||
def get_search():
|
||||
|
@ -337,20 +337,3 @@ def get_search():
|
|||
resp = make_response(json.dumps(data), 200)
|
||||
resp.mimetype = 'application/json'
|
||||
return resp
|
||||
|
||||
# Note: This is *not* part of the Docker index spec. This is here for our own health check,
|
||||
# since we have nginx handle the _ping below.
|
||||
@index.route('/_internal_ping')
|
||||
@anon_allowed
|
||||
def internal_ping():
|
||||
return make_response('true', 200)
|
||||
|
||||
@index.route('/_ping')
|
||||
@index.route('/_ping')
|
||||
@anon_allowed
|
||||
def ping():
|
||||
# NOTE: any changes made here must also be reflected in the nginx config
|
||||
response = make_response('true', 200)
|
||||
response.headers['X-Docker-Registry-Version'] = '0.6.0'
|
||||
response.headers['X-Docker-Registry-Standalone'] = '0'
|
||||
return response
|
|
@ -16,13 +16,13 @@ from auth.permissions import (ReadRepositoryPermission,
|
|||
ModifyRepositoryPermission)
|
||||
from data import model, database
|
||||
from util import gzipstream
|
||||
from endpoints.v1 import v1_bp
|
||||
from endpoints.decorators import anon_protect
|
||||
|
||||
|
||||
registry = Blueprint('registry', __name__)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SocketReader(object):
|
||||
def __init__(self, fp):
|
||||
self._fp = fp
|
||||
|
@ -93,7 +93,7 @@ def set_cache_headers(f):
|
|||
return wrapper
|
||||
|
||||
|
||||
@registry.route('/images/<image_id>/layer', methods=['HEAD'])
|
||||
@v1_bp.route('/images/<image_id>/layer', methods=['HEAD'])
|
||||
@process_auth
|
||||
@extract_namespace_repo_from_session
|
||||
@require_completion
|
||||
|
@ -127,7 +127,7 @@ def head_image_layer(namespace, repository, image_id, headers):
|
|||
abort(403)
|
||||
|
||||
|
||||
@registry.route('/images/<image_id>/layer', methods=['GET'])
|
||||
@v1_bp.route('/images/<image_id>/layer', methods=['GET'])
|
||||
@process_auth
|
||||
@extract_namespace_repo_from_session
|
||||
@require_completion
|
||||
|
@ -171,7 +171,7 @@ def get_image_layer(namespace, repository, image_id, headers):
|
|||
abort(403)
|
||||
|
||||
|
||||
@registry.route('/images/<image_id>/layer', methods=['PUT'])
|
||||
@v1_bp.route('/images/<image_id>/layer', methods=['PUT'])
|
||||
@process_auth
|
||||
@extract_namespace_repo_from_session
|
||||
@anon_protect
|
||||
|
@ -277,7 +277,7 @@ def put_image_layer(namespace, repository, image_id):
|
|||
return make_response('true', 200)
|
||||
|
||||
|
||||
@registry.route('/images/<image_id>/checksum', methods=['PUT'])
|
||||
@v1_bp.route('/images/<image_id>/checksum', methods=['PUT'])
|
||||
@process_auth
|
||||
@extract_namespace_repo_from_session
|
||||
@anon_protect
|
||||
|
@ -352,7 +352,7 @@ def put_image_checksum(namespace, repository, image_id):
|
|||
return make_response('true', 200)
|
||||
|
||||
|
||||
@registry.route('/images/<image_id>/json', methods=['GET'])
|
||||
@v1_bp.route('/images/<image_id>/json', methods=['GET'])
|
||||
@process_auth
|
||||
@extract_namespace_repo_from_session
|
||||
@require_completion
|
||||
|
@ -384,7 +384,7 @@ def get_image_json(namespace, repository, image_id, headers):
|
|||
return response
|
||||
|
||||
|
||||
@registry.route('/images/<image_id>/ancestry', methods=['GET'])
|
||||
@v1_bp.route('/images/<image_id>/ancestry', methods=['GET'])
|
||||
@process_auth
|
||||
@extract_namespace_repo_from_session
|
||||
@require_completion
|
||||
|
@ -438,7 +438,7 @@ def store_checksum(image_storage, checksum):
|
|||
image_storage.save()
|
||||
|
||||
|
||||
@registry.route('/images/<image_id>/json', methods=['PUT'])
|
||||
@v1_bp.route('/images/<image_id>/json', methods=['PUT'])
|
||||
@process_auth
|
||||
@extract_namespace_repo_from_session
|
||||
@anon_protect
|
|
@ -11,14 +11,13 @@ from auth.permissions import (ReadRepositoryPermission,
|
|||
ModifyRepositoryPermission)
|
||||
from data import model
|
||||
from endpoints.decorators import anon_protect
|
||||
from endpoints.v1 import v1_bp
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
tags = Blueprint('tags', __name__)
|
||||
|
||||
|
||||
@tags.route('/repositories/<path:repository>/tags',
|
||||
@v1_bp.route('/repositories/<path:repository>/tags',
|
||||
methods=['GET'])
|
||||
@process_auth
|
||||
@anon_protect
|
||||
|
@ -34,7 +33,7 @@ def get_tags(namespace, repository):
|
|||
abort(403)
|
||||
|
||||
|
||||
@tags.route('/repositories/<path:repository>/tags/<tag>',
|
||||
@v1_bp.route('/repositories/<path:repository>/tags/<tag>',
|
||||
methods=['GET'])
|
||||
@process_auth
|
||||
@anon_protect
|
||||
|
@ -51,7 +50,7 @@ def get_tag(namespace, repository, tag):
|
|||
abort(403)
|
||||
|
||||
|
||||
@tags.route('/repositories/<path:repository>/tags/<tag>',
|
||||
@v1_bp.route('/repositories/<path:repository>/tags/<tag>',
|
||||
methods=['PUT'])
|
||||
@process_auth
|
||||
@anon_protect
|
||||
|
@ -74,7 +73,7 @@ def put_tag(namespace, repository, tag):
|
|||
abort(403)
|
||||
|
||||
|
||||
@tags.route('/repositories/<path:repository>/tags/<tag>',
|
||||
@v1_bp.route('/repositories/<path:repository>/tags/<tag>',
|
||||
methods=['DELETE'])
|
||||
@process_auth
|
||||
@anon_protect
|
62
endpoints/v2/__init__.py
Normal file
62
endpoints/v2/__init__.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import logging
|
||||
|
||||
from flask import Blueprint, make_response
|
||||
from functools import wraps
|
||||
|
||||
from endpoints.decorators import anon_protect, anon_allowed
|
||||
from auth.jwt_auth import process_jwt_auth
|
||||
from auth.auth_context import get_grant_user_context
|
||||
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
|
||||
AdministerRepositoryPermission)
|
||||
from data import model
|
||||
from util.http import abort
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
v2_bp = Blueprint('v2', __name__)
|
||||
|
||||
|
||||
def _require_repo_permission(permission_class, allow_public=False):
|
||||
def wrapper(func):
|
||||
@wraps(func)
|
||||
def wrapped(namespace, repo_name, *args, **kwargs):
|
||||
logger.debug('Checking permission %s for repo: %s/%s', permission_class, namespace, repo_name)
|
||||
permission = permission_class(namespace, repo_name)
|
||||
if (permission.can() or
|
||||
(allow_public and
|
||||
model.repository_is_public(namespace, repo_name))):
|
||||
return func(namespace, repo_name, *args, **kwargs)
|
||||
raise abort(401)
|
||||
return wrapped
|
||||
return wrapper
|
||||
|
||||
|
||||
require_repo_read = _require_repo_permission(ReadRepositoryPermission, True)
|
||||
require_repo_write = _require_repo_permission(ModifyRepositoryPermission)
|
||||
require_repo_admin = _require_repo_permission(AdministerRepositoryPermission)
|
||||
|
||||
|
||||
def get_input_stream(flask_request):
|
||||
if flask_request.headers.get('transfer-encoding') == 'chunked':
|
||||
return flask_request.environ['wsgi.input']
|
||||
return flask_request.stream
|
||||
|
||||
|
||||
@v2_bp.route('/')
|
||||
@process_jwt_auth
|
||||
@anon_allowed
|
||||
def v2_support_enabled():
|
||||
response = make_response('true', 200)
|
||||
|
||||
if get_grant_user_context() is None:
|
||||
response = make_response('true', 401)
|
||||
response.headers['WWW-Authenticate'] = 'Bearer realm="192.168.59.3:5000/v2/auth",service="quay"'
|
||||
|
||||
response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
|
||||
return response
|
||||
|
||||
|
||||
from endpoints.v2 import v2auth
|
||||
from endpoints.v2 import manifest
|
||||
from endpoints.v2 import blobs
|
20
endpoints/v2/blobs.py
Normal file
20
endpoints/v2/blobs.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import logging
|
||||
|
||||
from flask import make_response
|
||||
|
||||
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, require_repo_admin
|
||||
from auth.jwt_auth import process_jwt_auth
|
||||
from auth.permissions import ReadRepositoryPermission
|
||||
from endpoints.decorators import anon_protect
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@v2_bp.route('/<namespace>/<repo_name>/blobs/<tarsum>', methods=['HEAD'])
|
||||
@process_jwt_auth
|
||||
@require_repo_read
|
||||
@anon_protect
|
||||
def check_blob_existence(namespace, repo_name, tarsum):
|
||||
logger.debug('Fetching blob with tarsum: %s', tarsum)
|
||||
return make_response('Blob {0}'.format(tarsum))
|
46
endpoints/v2/digest_tools.py
Normal file
46
endpoints/v2/digest_tools.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import re
|
||||
import os.path
|
||||
import hashlib
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
Digest = namedtuple('Digest', ['is_tarsum', 'tarsum_version', 'hash_alg', 'hash_bytes'])
|
||||
|
||||
|
||||
DIGEST_PATTERN = r'(tarsum\.(v[\w]+)\+)?([\w]+):([0-9a-f]+)'
|
||||
DIGEST_REGEX = re.compile(DIGEST_PATTERN)
|
||||
|
||||
|
||||
class InvalidDigestException(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def parse_digest(digest):
|
||||
""" Returns the digest parsed out to its components. """
|
||||
match = DIGEST_REGEX.match(digest)
|
||||
if match is None or match.end() != len(digest):
|
||||
raise InvalidDigestException('Not a valid digest: %s', digest)
|
||||
|
||||
is_tarsum = match.group(1) is not None
|
||||
return Digest(is_tarsum, match.group(2), match.group(3), match.group(4))
|
||||
|
||||
|
||||
def content_path(digest):
|
||||
""" Returns a relative path to the parsed digest. """
|
||||
parsed = parse_digest(digest)
|
||||
components = []
|
||||
|
||||
if parsed.is_tarsum:
|
||||
components.extend(['tarsum', parsed.tarsum_version])
|
||||
|
||||
prefix = parsed.hash_bytes[0:2].zfill(2)
|
||||
components.extend([parsed.hash_alg, prefix, parsed.hash_bytes])
|
||||
|
||||
return os.path.join(*components)
|
||||
|
||||
|
||||
def sha256_digest(content):
|
||||
""" Returns a sha256 hash of the content bytes in digest form. """
|
||||
digest = hashlib.sha256(content)
|
||||
return 'sha256:{0}'.format(digest.hexdigest())
|
63
endpoints/v2/manifest.py
Normal file
63
endpoints/v2/manifest.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
import logging
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
from flask import make_response, request
|
||||
|
||||
from app import storage
|
||||
from auth.jwt_auth import process_jwt_auth
|
||||
from auth.permissions import ReadRepositoryPermission
|
||||
from endpoints.decorators import anon_protect
|
||||
from endpoints.v2 import (v2_bp, require_repo_read, require_repo_write, require_repo_admin,
|
||||
get_input_stream)
|
||||
from endpoints.v2 import digest_tools
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
VALID_TAG_PATTERN = r'[\w][\w.-]{0,127}'
|
||||
VALID_TAG_REGEX = re.compile(VALID_TAG_PATTERN)
|
||||
|
||||
|
||||
def is_tag_name(reference):
|
||||
match = VALID_TAG_REGEX.match(reference)
|
||||
return match is not None and match.end() == len(reference)
|
||||
|
||||
|
||||
@v2_bp.route('/<namespace>/<repo_name>/manifests/<regex("' + VALID_TAG_PATTERN + '"):tag_name>',
|
||||
methods=['GET'])
|
||||
@process_jwt_auth
|
||||
@require_repo_read
|
||||
@anon_protect
|
||||
def fetch_manifest_by_tagname(namespace, repo_name, tag_name):
|
||||
logger.debug('Fetching tag manifest with name: %s', tag_name)
|
||||
return make_response('Manifest {0}'.format(tag_name))
|
||||
|
||||
|
||||
@v2_bp.route('/<namespace>/<repo_name>/manifests/<regex("' + VALID_TAG_PATTERN + '"):tag_name>',
|
||||
methods=['PUT'])
|
||||
@process_jwt_auth
|
||||
@require_repo_write
|
||||
@anon_protect
|
||||
def write_manifest_by_tagname(namespace, repo_name, tag_name):
|
||||
manifest_data = request.data
|
||||
logger.debug('Manifest data: %s', manifest_data)
|
||||
response = make_response('OK', 202)
|
||||
response.headers['Docker-Content-Digest'] = digest_tools.sha256_digest(manifest_data)
|
||||
response.headers['Location'] = 'https://fun.com'
|
||||
return response
|
||||
|
||||
|
||||
@v2_bp.route('/<namespace>/<repo_name>/manifests/<regex("' + digest_tools.DIGEST_PATTERN + '"):tag_digest>',
|
||||
methods=['PUT'])
|
||||
@process_jwt_auth
|
||||
@require_repo_write
|
||||
@anon_protect
|
||||
def write_manifest(namespace, repo_name, tag_digest):
|
||||
logger.debug('Writing tag manifest with name: %s', tag_digest)
|
||||
|
||||
manifest_path = digest_tools.content_path(tag_digest)
|
||||
storage.stream_write('local_us', manifest_path, get_input_stream(request))
|
||||
|
||||
return make_response('Manifest {0}'.format(tag_digest))
|
12
endpoints/v2/registry.py
Normal file
12
endpoints/v2/registry.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
import logging
|
||||
|
||||
from endpoints.v2 import v2_bp
|
||||
|
||||
|
||||
logging.getLogger(__name__)
|
||||
|
||||
|
||||
@v2_bp.route()
|
||||
|
||||
@process_auth
|
||||
@anon_protect
|
94
endpoints/v2/v2auth.py
Normal file
94
endpoints/v2/v2auth.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
import logging
|
||||
import re
|
||||
import time
|
||||
import jwt
|
||||
|
||||
from flask import request, jsonify, abort
|
||||
|
||||
from data import model
|
||||
from auth.auth import process_auth
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from auth.permissions import (ModifyRepositoryPermission, ReadRepositoryPermission,
|
||||
CreateRepositoryPermission)
|
||||
from endpoints.v2 import v2_bp
|
||||
from util.cache import no_cache
|
||||
from util.names import parse_namespace_repository
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SCOPE_REGEX = re.compile(
|
||||
r'repository:([\.a-zA-Z0-9_\-]+/[\.a-zA-Z0-9_\-]+):(((push|pull|\*),)*(push|pull|\*))'
|
||||
)
|
||||
|
||||
|
||||
@v2_bp.route('/auth')
|
||||
@process_auth
|
||||
@no_cache
|
||||
def generate_registry_jwt():
|
||||
audience_param = request.args.get('service')
|
||||
logger.debug('Request audience: %s', audience_param)
|
||||
|
||||
scope_param = request.args.get('scope')
|
||||
logger.debug('Scope request: %s', scope_param)
|
||||
|
||||
user = get_authenticated_user()
|
||||
|
||||
access = []
|
||||
if scope_param is not None:
|
||||
match = SCOPE_REGEX.match(scope_param)
|
||||
if match is None or match.end() != len(scope_param):
|
||||
logger.debug('Match: %s', match)
|
||||
logger.debug('End: %s', match.end())
|
||||
logger.debug('len: %s', len(scope_param))
|
||||
logger.warning('Unable to decode repository and actions: %s', scope_param)
|
||||
abort(400)
|
||||
|
||||
logger.debug('Match: %s', match.groups())
|
||||
|
||||
namespace_and_repo = match.group(1)
|
||||
actions = match.group(2).split(',')
|
||||
|
||||
namespace, reponame = parse_namespace_repository(namespace_and_repo)
|
||||
if 'pull' in actions and 'push' in actions:
|
||||
repo = model.get_repository(namespace, reponame)
|
||||
if repo:
|
||||
if not ModifyRepositoryPermission(namespace, reponame):
|
||||
abort(403)
|
||||
else:
|
||||
if not CreateRepositoryPermission(namespace):
|
||||
abort(403)
|
||||
logger.debug('Creating repository: %s/%s', namespace, reponame)
|
||||
model.create_repository(namespace, reponame, user)
|
||||
elif 'pull' in actions:
|
||||
if not ReadRepositoryPermission(namespace, reponame):
|
||||
abort(403)
|
||||
|
||||
|
||||
access.append({
|
||||
'type': 'repository',
|
||||
'name': namespace_and_repo,
|
||||
'actions': actions,
|
||||
})
|
||||
|
||||
token_data = {
|
||||
'iss': 'token-issuer',
|
||||
'aud': audience_param,
|
||||
'nbf': int(time.time()),
|
||||
'exp': int(time.time() + 60),
|
||||
'sub': user.username,
|
||||
'access': access,
|
||||
}
|
||||
|
||||
with open('/Users/jake/Projects/registry-v2/ca/quay.host.crt') as cert_file:
|
||||
certificate = ''.join(cert_file.readlines()[1:-1]).rstrip('\n')
|
||||
|
||||
token_headers = {
|
||||
'x5c': [certificate],
|
||||
}
|
||||
|
||||
with open('/Users/jake/Projects/registry-v2/ca/quay.host.key.insecure') as private_key_file:
|
||||
private_key = private_key_file.read()
|
||||
|
||||
return jsonify({'token':jwt.encode(token_data, private_key, 'RS256', headers=token_headers)})
|
Reference in a new issue