def get_image_ancestry(namespace, repository, image_id, headers): logger.debug("Checking repo permissions") permission = ReadRepositoryPermission(namespace, repository) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image") if not permission.can() and not (repository_ref is not None and repository_ref.is_public): abort(403) logger.debug("Looking up repo image") legacy_image = registry_model.get_legacy_image(repository_ref, image_id, include_parents=True) if legacy_image is None: abort(404, "Image %(image_id)s not found", issue="unknown-image", image_id=image_id) # NOTE: We can not use jsonify here because we are returning a list not an object. ancestor_ids = [legacy_image.docker_image_id ] + [a.docker_image_id for a in legacy_image.parents] response = make_response(json.dumps(ancestor_ids), 200) response.headers.extend(headers) return response
def get_image_json(namespace, repository, image_id, headers): logger.debug("Checking repo permissions") permission = ReadRepositoryPermission(namespace, repository) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image") if not permission.can() and not (repository_ref is not None and repository_ref.is_public): abort(403) logger.debug("Looking up repo image") legacy_image = registry_model.get_legacy_image(repository_ref, image_id, include_blob=True) if legacy_image is None: flask_abort(404) size = legacy_image.blob.compressed_size if size is not None: # Note: X-Docker-Size is optional and we *can* end up with a NULL image_size, # so handle this case rather than failing. headers["X-Docker-Size"] = str(size) response = make_response(legacy_image.v1_metadata_string, 200) response.headers.extend(headers) return response
def wrapper(*args, **kwargs): if 'namespace' not in session or 'repository' not in session: logger.error( 'Unable to load namespace or repository from session: %s', session) abort(400, message='Missing namespace in request') return func(session['namespace'], session['repository'], *args, **kwargs)
def get_user(): context = get_authenticated_context() if not context or context.is_anonymous: abort(404) return jsonify({ "username": context.credential_username, "email": None, })
def wrapper(*args, **kwargs): if "namespace" not in session or "repository" not in session: logger.error( "Unable to load namespace or repository from session: %s", session) abort(400, message="Missing namespace in request") return func(session["namespace"], session["repository"], *args, **kwargs)
def wrapper(*args, **kwargs): result = validate_session_cookie() if result.has_nonrobot_user: result.apply_to_context() authentication_count.labels(result.kind, True).inc() return func(*args, **kwargs) elif not result.missing: authentication_count.labels(result.kind, False).inc() abort(401, message="Method requires login and no valid login could be loaded.")
def wrapper(namespace_name, repo_name, *args, **kwargs): namespace = model.user.get_namespace_user(namespace_name) is_namespace_enabled = namespace is not None and namespace.enabled if not is_namespace_enabled: abort( 400, message= "Namespace is disabled. Please contact your system administrator." ) return f(namespace_name, repo_name, *args, **kwargs)
def wrapped(*args, **kwargs): namespace_name, repo_name = get_reponame_method(*args, **kwargs) image_repo = model.repository.get_repository( namespace_name, repo_name, kind_filter="image" ) if image_repo is not None: logger.debug("Tried to invoked a CNR method on an image repository") abort( 405, message="Cannot push an application to an image repository with the same name", ) return func(*args, **kwargs)
def get_image_layer(namespace, repository, image_id, headers): permission = ReadRepositoryPermission(namespace, repository) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image") logger.debug("Checking repo permissions") if permission.can() or (repository_ref is not None and repository_ref.is_public): if repository_ref is None: abort(404) legacy_image = registry_model.get_legacy_image(repository_ref, image_id, include_blob=True) if legacy_image is None: abort(404, "Image %(image_id)s not found", issue="unknown-image", image_id=image_id) path = legacy_image.blob.storage_path image_pulled_bytes.labels("v1").inc(legacy_image.blob.compressed_size) try: logger.debug("Looking up the direct download URL for path: %s", path) direct_download_url = store.get_direct_download_url( legacy_image.blob.placements, path, get_request_ip() ) if direct_download_url: logger.debug("Returning direct download URL") resp = redirect(direct_download_url) return resp # Close the database handle here for this process before we send the long download. database.close_db_filter(None) logger.debug("Streaming layer data") return Response(store.stream_read(legacy_image.blob.placements, path), headers=headers) except (IOError, AttributeError): logger.exception("Image layer data not found") abort(404, "Image %(image_id)s not found", issue="unknown-image", image_id=image_id) abort(403)
def wrapper(namespace, repository, *args, **kwargs): image_id = kwargs["image_id"] repository_ref = registry_model.lookup_repository(namespace, repository) if repository_ref is not None: legacy_image = registry_model.get_legacy_image(repository_ref, image_id) if legacy_image is not None and legacy_image.uploading: abort( 400, "Image %(image_id)s is being uploaded, retry later", issue="upload-in-progress", image_id=image_id, ) return f(namespace, repository, *args, **kwargs)
def wrapper(*args, **kwargs): result = validate_session_cookie() if result.has_nonrobot_user: result.apply_to_context() metric_queue.authentication_count.Inc( labelvalues=[result.kind, True]) return func(*args, **kwargs) elif not result.missing: metric_queue.authentication_count.Inc( labelvalues=[result.kind, False]) abort( 401, message='Method requires login and no valid login could be loaded.' )
def update_user(username): permission = UserAdminPermission(username) if permission.can(): update_request = request.get_json() if "password" in update_request: logger.debug("Updating user password") model.user.change_password(get_authenticated_user(), update_request["password"]) return jsonify({ "username": get_authenticated_user().username, "email": get_authenticated_user().email, }) abort(403)
def v2_support_enabled(): docker_ver = docker_version(request.user_agent.string) # Check if our version is one of the blacklisted versions, if we can't # identify the version (None) we will fail open and assume that it is # newer and therefore should not be blacklisted. if docker_ver is not None and Spec(app.config['BLACKLIST_V2_SPEC']).match(docker_ver): abort(404) response = make_response('true', 200) if get_authenticated_context() is None: response = make_response('true', 401) response.headers.extend(get_auth_headers()) return response
def update_user(username): permission = UserAdminPermission(username) if permission.can(): update_request = request.get_json() if 'password' in update_request: logger.debug('Updating user password') model.user.change_password(get_authenticated_user(), update_request['password']) return jsonify({ 'username': get_authenticated_user().username, 'email': get_authenticated_user().email, }) abort(403)
def verify_csrf(session_token_name=_QUAY_CSRF_TOKEN_NAME, request_token_name=_QUAY_CSRF_TOKEN_NAME, check_header=True): """ Verifies that the CSRF token with the given name is found in the session and that the matching token is found in the request args or values. """ token = str(session.get(session_token_name, '')) found_token = str(request.values.get(request_token_name, '')) if check_header and not found_token: found_token = str(request.headers.get(_QUAY_CSRF_HEADER_NAME, '')) if not token or not found_token or not hmac.compare_digest( token, found_token): msg = 'CSRF Failure. Session token (%s) was %s and request token (%s) was %s' logger.error(msg, session_token_name, token, request_token_name, found_token) abort(403, message='CSRF token was invalid or missing.')
def decorated(*args, **kwargs): if namespace_name_kwarg in kwargs: namespace_name = kwargs[namespace_name_kwarg] else: namespace_name = args[0] if features.RESTRICTED_V1_PUSH: whitelist = app.config.get('V1_PUSH_WHITELIST') or [] logger.debug('V1 push is restricted to whitelist: %s', whitelist) if namespace_name not in whitelist: abort( 405, message= ('V1 push support has been deprecated. To enable for this ' + 'namespace, please contact support.')) return wrapped(*args, **kwargs)
def _user_teams(user, resource): changed = False p_exact_teams = resource["exact_teams"] p_add_teams = resource["add_teams"] p_remove_teams = resource["remove_teams"] team_names = p_exact_teams or p_add_teams or p_remove_teams if team_names is None: return False teams = [] for name in team_names: try: teams.append(Team.get(Team.name == name)) except model.InvalidTeamException: abort(400, message="Team '%s' does not exist" % name) teams = set(teams) current_teams = set(Team.select().join(TeamMember).join(User)).where( User.username == user.username) teams_to_add = teams - current_teams teams_to_remove = current_teams - teams if p_add_teams: teams_to_remove = [] elif p_remove_teams: teams_to_add = [] for team in teams_to_add: changed = True model.team.add_user_to_team(user, team) query = TeamMember.select().join(User).switch(TeamMember).join(Team).join( TeamRole) for team in teams_to_remove: changed = True found = list( query.where(User.username == user.username, Team.name == team.name)) found[0].delete_instance() return changed
def update_images(namespace_name, repo_name): permission = ModifyRepositoryPermission(namespace_name, repo_name) if permission.can(): logger.debug("Looking up repository") repository_ref = registry_model.lookup_repository(namespace_name, repo_name, kind_filter="image") if repository_ref is None: # Make sure the repo actually exists. image_pushes.labels("v1", 404, "").inc() abort(404, message="Unknown repository", issue="unknown-repo") builder = lookup_manifest_builder(repository_ref, session.get("manifest_builder"), storage, docker_v2_signing_key) if builder is None: image_pushes.labels("v1", 400, "").inc() abort(400) # Generate a job for each notification that has been added to this repo logger.debug("Adding notifications for repository") event_data = { "updated_tags": [tag.name for tag in builder.committed_tags], } builder.done() track_and_log("push_repo", repository_ref) spawn_notification(repository_ref, "repo_push", event_data) image_pushes.labels("v1", 204, "").inc() return make_response("Updated", 204) image_pushes.labels("v1", 403, "").inc() abort(403)
def head_image_layer(namespace, repository, image_id, headers): permission = ReadRepositoryPermission(namespace, repository) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image") logger.debug("Checking repo permissions") if permission.can() or (repository_ref is not None and repository_ref.is_public): if repository_ref is None: abort(404) logger.debug("Looking up placement locations") legacy_image = registry_model.get_legacy_image(repository_ref, image_id, include_blob=True) if legacy_image is None: logger.debug("Could not find any blob placement locations") abort(404, "Image %(image_id)s not found", issue="unknown-image", image_id=image_id) # Add the Accept-Ranges header if the storage engine supports resumable # downloads. extra_headers = {} if store.get_supports_resumable_downloads(legacy_image.blob.placements): logger.debug("Storage supports resumable downloads") extra_headers["Accept-Ranges"] = "bytes" resp = make_response("") resp.headers.extend(headers) resp.headers.extend(extra_headers) return resp abort(403)
def update_images(namespace_name, repo_name): permission = ModifyRepositoryPermission(namespace_name, repo_name) if permission.can(): logger.debug('Looking up repository') repository_ref = registry_model.lookup_repository(namespace_name, repo_name, kind_filter='image') if repository_ref is None: # Make sure the repo actually exists. abort(404, message='Unknown repository', issue='unknown-repo') builder = lookup_manifest_builder(repository_ref, session.get('manifest_builder'), storage, docker_v2_signing_key) if builder is None: abort(400) # Generate a job for each notification that has been added to this repo logger.debug('Adding notifications for repository') event_data = { 'updated_tags': [tag.name for tag in builder.committed_tags], } builder.done() track_and_log('push_repo', repository_ref) spawn_notification(repository_ref, 'repo_push', event_data) metric_queue.repository_push.Inc( labelvalues=[namespace_name, repo_name, 'v1', True]) return make_response('Updated', 204) abort(403)
def get_repository_images(namespace_name, repo_name): repository_ref = registry_model.lookup_repository( namespace_name, repo_name, kind_filter="image" ) permission = ReadRepositoryPermission(namespace_name, repo_name) if permission.can() or (repository_ref and repository_ref.is_public): # We can't rely on permissions to tell us if a repo exists anymore if repository_ref is None: abort(404, message="Unknown repository", issue="unknown-repo") logger.debug("Building repository image response") resp = make_response(json.dumps([]), 200) resp.mimetype = "application/json" track_and_log( "pull_repo", repository_ref, analytics_name="pull_repo_100x", analytics_sample=0.01 ) metric_queue.repository_pull.Inc(labelvalues=[namespace_name, repo_name, "v1", True]) return resp abort(403)
def get_image_layer(namespace, repository, image_id, headers): permission = ReadRepositoryPermission(namespace, repository) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter='image') logger.debug('Checking repo permissions') if permission.can() or (repository_ref is not None and repository_ref.is_public): if repository_ref is None: abort(404) legacy_image = registry_model.get_legacy_image(repository_ref, image_id, include_blob=True) if legacy_image is None: abort(404, 'Image %(image_id)s not found', issue='unknown-image', image_id=image_id) path = legacy_image.blob.storage_path metric_queue.pull_byte_count.Inc(legacy_image.blob.compressed_size, labelvalues=['v1']) try: logger.debug('Looking up the direct download URL for path: %s', path) direct_download_url = store.get_direct_download_url( legacy_image.blob.placements, path, get_request_ip()) if direct_download_url: logger.debug('Returning direct download URL') resp = redirect(direct_download_url) return resp # Close the database handle here for this process before we send the long download. database.close_db_filter(None) logger.debug('Streaming layer data') return Response(store.stream_read(legacy_image.blob.placements, path), headers=headers) except (IOError, AttributeError): logger.exception('Image layer data not found') abort(404, 'Image %(image_id)s not found', issue='unknown-image', image_id=image_id) abort(403)
def attach_bitbucket_build_trigger(trigger_uuid): trigger = model.build.get_build_trigger(trigger_uuid) if not trigger or trigger.service.name != BitbucketBuildTrigger.service_name(): abort(404) if trigger.connected_user != current_user.db_user(): abort(404) verifier = request.args.get("oauth_verifier") handler = BuildTriggerHandler.get_handler(trigger) result = handler.exchange_verifier(verifier) if not result: trigger.delete_instance() return "Token has expired" namespace = trigger.repository.namespace_user.username repository = trigger.repository.name repo_path = "%s/%s" % (namespace, repository) full_url = url_for("web.buildtrigger", path=repo_path, trigger=trigger.uuid) logger.debug("Redirecting to full url: %s", full_url) return redirect(full_url)
def attach_github_build_trigger(namespace_name, repo_name): permission = AdministerRepositoryPermission(namespace_name, repo_name) if permission.can(): code = request.args.get('code') token = github_trigger.exchange_code_for_token(app.config, client, code) repo = model.repository.get_repository(namespace_name, repo_name) if not repo: msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name) abort(404, message=msg) elif repo.kind.name != 'image': abort(501) trigger = model.build.create_build_trigger(repo, 'github', token, current_user.db_user()) repo_path = '%s/%s' % (namespace_name, repo_name) full_url = url_for('web.buildtrigger', path=repo_path, trigger=trigger.uuid) logger.debug('Redirecting to full url: %s', full_url) return redirect(full_url) abort(403)
def put_image_json(namespace, repository, image_id): logger.debug("Checking repo permissions") permission = ModifyRepositoryPermission(namespace, repository) if not permission.can(): abort(403) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image") if repository_ref is None: abort(403) builder = lookup_manifest_builder(repository_ref, session.get("manifest_builder"), store, docker_v2_signing_key) if builder is None: abort(400) logger.debug("Parsing image JSON") try: uploaded_metadata = request.data uploaded_metadata_string = uploaded_metadata.decode("utf-8") data = json.loads(uploaded_metadata_string) except ValueError: pass if not data or not isinstance(data, dict): abort( 400, "Invalid JSON for image: %(image_id)s\nJSON: %(json)s", issue="invalid-request", image_id=image_id, json=request.data, ) if "id" not in data: abort( 400, "Missing key `id` in JSON for image: %(image_id)s", issue="invalid-request", image_id=image_id, ) if image_id != data["id"]: abort( 400, "JSON data contains invalid id for image: %(image_id)s", issue="invalid-request", image_id=image_id, ) logger.debug("Looking up repo image") location_pref = store.preferred_locations[0] username = get_authenticated_user() and get_authenticated_user().username layer = builder.start_layer( image_id, uploaded_metadata_string, location_pref, username, app.config["PUSH_TEMP_TAG_EXPIRATION_SEC"], ) if layer is None: abort( 400, "Image %(image_id)s has invalid metadata", issue="invalid-request", image_id=image_id, ) return make_response("true", 200)
def put_repository_auth(namespace_name, repo_name): abort(501, "Not Implemented", issue="not-implemented")
def delete_repository_images(namespace_name, repo_name): abort(501, "Not Implemented", issue="not-implemented")
def put_image_checksum(namespace, repository, image_id): logger.debug("Checking repo permissions") permission = ModifyRepositoryPermission(namespace, repository) if not permission.can(): abort(403) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image") if repository_ref is None: abort(403) # Docker Version < 0.10 (tarsum+sha): old_checksum = request.headers.get("X-Docker-Checksum") # Docker Version >= 0.10 (sha): new_checksum = request.headers.get("X-Docker-Checksum-Payload") checksum = new_checksum or old_checksum if not checksum: abort( 400, "Missing checksum for image %(image_id)s", issue="missing-checksum", image_id=image_id, ) logger.debug("Checking for image in manifest builder") builder = lookup_manifest_builder(repository_ref, session.get("manifest_builder"), store, docker_v2_signing_key) if builder is None: abort(400) layer = builder.lookup_layer(image_id) if layer is None: abort(404) if old_checksum: builder.save_precomputed_checksum(layer, checksum) return make_response("true", 200) if not builder.validate_layer_checksum(layer, checksum): logger.debug( "put_image_checksum: Wrong checksum. Given: %s and expected: %s", checksum, builder.get_layer_checksums(layer), ) abort( 400, "Checksum mismatch for image: %(image_id)s", issue="checksum-mismatch", image_id=image_id, ) return make_response("true", 200)
def create_repository(namespace_name, repo_name): # Verify that the repository name is valid. if not REPOSITORY_NAME_REGEX.match(repo_name): abort( 400, message= "Invalid repository name. Repository names cannot contain slashes." ) logger.debug("Looking up repository %s/%s", namespace_name, repo_name) repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None and get_authenticated_user() is None: logger.debug("Attempt to create repository %s/%s without user auth", namespace_name, repo_name) abort( 401, message= 'Cannot create a repository as a guest. Please login via "docker login" first.', issue="no-login", ) elif repository_ref: modify_perm = ModifyRepositoryPermission(namespace_name, repo_name) if not modify_perm.can(): abort( 403, message= "You do not have permission to modify repository %(namespace)s/%(repository)s", issue="no-repo-write-permission", namespace=namespace_name, repository=repo_name, ) elif repository_ref.kind != "image": msg = ( "This repository is for managing %s resources and not container images." % repository_ref.kind) abort(405, message=msg, namespace=namespace_name) else: create_perm = CreateRepositoryPermission(namespace_name) if not create_perm.can(): logger.warning( "Attempt to create a new repo %s/%s with insufficient perms", namespace_name, repo_name, ) msg = 'You do not have permission to create repositories in namespace "%(namespace)s"' abort(403, message=msg, issue="no-create-permission", namespace=namespace_name) # Attempt to create the new repository. logger.debug( "Creating repository %s/%s with owner: %s", namespace_name, repo_name, get_authenticated_user().username, ) repository_ref = model.repository.create_repository( namespace_name, repo_name, get_authenticated_user()) if get_authenticated_user(): user_event_data = { "action": "push_start", "repository": repo_name, "namespace": namespace_name, } event = userevents.get_event(get_authenticated_user().username) event.publish_event_data("docker-cli", user_event_data) # Start a new builder for the repository and save its ID in the session. assert repository_ref builder = create_manifest_builder(repository_ref, storage, docker_v2_signing_key) logger.debug("Started repo push with manifest builder %s", builder) if builder is None: abort(404, message="Unknown repository", issue="unknown-repo") session["manifest_builder"] = builder.builder_id return make_response("Created", 201)
def create_user(): user_data = request.get_json() if not user_data or not "username" in user_data: abort(400, "Missing username") username = user_data["username"] password = user_data.get("password", "") # UGH! we have to use this response when the login actually worked, in order # to get the CLI to try again with a get, and then tell us login succeeded. success = make_response('"Username or email already exists"', 400) result, kind = validate_credentials(username, password) if not result.auth_valid: if kind == CredentialKind.token: abort(400, "Invalid access token.", issue="invalid-access-token") if kind == CredentialKind.robot: abort(400, "Invalid robot account or password.", issue="robot-login-failure") if kind == CredentialKind.oauth_token: abort(400, "Invalid oauth access token.", issue="invalid-oauth-access-token") if kind == CredentialKind.user: # Mark that the login failed. event = userevents.get_event(username) event.publish_event_data("docker-cli", {"action": "loginfailure"}) abort(400, result.error_message, issue="login-failure") # Default case: Just fail. abort(400, result.error_message, issue="login-failure") if result.has_nonrobot_user: # Mark that the user was logged in. event = userevents.get_event(username) event.publish_event_data("docker-cli", {"action": "login"}) return success