2016-05-14 12:11:48 -04:00
|
|
|
module Auth
|
|
|
|
class ContainerRegistryAuthenticationService < BaseService
|
2017-02-21 18:32:18 -05:00
|
|
|
AUDIENCE = 'container_registry'.freeze
|
2016-05-14 12:15:19 -04:00
|
|
|
|
2016-09-16 03:59:10 -04:00
|
|
|
def execute(authentication_abilities:)
|
2016-09-20 11:07:34 -04:00
|
|
|
@authentication_abilities = authentication_abilities
|
2016-08-08 06:01:25 -04:00
|
|
|
|
2016-09-26 06:18:21 -04:00
|
|
|
return error('UNAVAILABLE', status: 404, message: 'registry not enabled') unless registry.enabled
|
2016-05-14 12:15:19 -04:00
|
|
|
|
2016-11-08 13:37:15 -05:00
|
|
|
unless scope || current_user || project
|
|
|
|
return error('DENIED', status: 403, message: 'access forbidden')
|
2016-05-14 12:11:48 -04:00
|
|
|
end
|
|
|
|
|
2016-05-14 19:23:31 -04:00
|
|
|
{ token: authorized_token(scope).encoded }
|
2016-05-14 12:11:48 -04:00
|
|
|
end
|
|
|
|
|
2016-12-15 22:24:05 -05:00
|
|
|
def self.full_access_token(*names)
|
|
|
|
names = names.flatten
|
2016-05-14 12:15:19 -04:00
|
|
|
registry = Gitlab.config.registry
|
2016-05-15 09:47:48 -04:00
|
|
|
token = JSONWebToken::RSAToken.new(registry.key)
|
2016-05-14 12:15:19 -04:00
|
|
|
token.issuer = registry.issuer
|
|
|
|
token.audience = AUDIENCE
|
2016-05-31 07:48:05 -04:00
|
|
|
token.expire_time = token_expire_at
|
2016-07-06 09:26:59 -04:00
|
|
|
|
2016-05-14 12:15:19 -04:00
|
|
|
token[:access] = names.map do |name|
|
2016-05-20 19:43:11 -04:00
|
|
|
{ type: 'repository', name: name, actions: %w(*) }
|
2016-05-14 12:15:19 -04:00
|
|
|
end
|
2016-07-19 08:17:47 -04:00
|
|
|
|
2016-05-14 12:15:19 -04:00
|
|
|
token.encoded
|
|
|
|
end
|
|
|
|
|
2016-07-19 08:17:47 -04:00
|
|
|
def self.token_expire_at
|
2018-02-02 13:39:55 -05:00
|
|
|
Time.now + Gitlab::CurrentSettings.container_registry_token_expire_delay.minutes
|
2016-07-19 08:17:47 -04:00
|
|
|
end
|
|
|
|
|
2016-05-14 12:11:48 -04:00
|
|
|
private
|
|
|
|
|
2016-05-14 19:23:31 -04:00
|
|
|
def authorized_token(*accesses)
|
2017-03-23 06:41:16 -04:00
|
|
|
JSONWebToken::RSAToken.new(registry.key).tap do |token|
|
|
|
|
token.issuer = registry.issuer
|
|
|
|
token.audience = params[:service]
|
|
|
|
token.subject = current_user.try(:username)
|
|
|
|
token.expire_time = self.class.token_expire_at
|
|
|
|
token[:access] = accesses.compact
|
|
|
|
end
|
2016-05-14 12:11:48 -04:00
|
|
|
end
|
|
|
|
|
2016-05-14 19:23:31 -04:00
|
|
|
def scope
|
2016-05-14 12:11:48 -04:00
|
|
|
return unless params[:scope]
|
|
|
|
|
2016-05-14 19:23:31 -04:00
|
|
|
@scope ||= process_scope(params[:scope])
|
2016-05-14 12:11:48 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def process_scope(scope)
|
|
|
|
type, name, actions = scope.split(':', 3)
|
|
|
|
actions = actions.split(',')
|
2017-03-29 07:01:48 -04:00
|
|
|
|
2017-10-10 15:15:38 -04:00
|
|
|
case type
|
|
|
|
when 'registry'
|
|
|
|
process_registry_access(type, name, actions)
|
|
|
|
when 'repository'
|
|
|
|
path = ContainerRegistry::Path.new(name)
|
|
|
|
process_repository_access(type, path, actions)
|
2017-10-08 14:36:45 -04:00
|
|
|
end
|
2017-10-10 15:15:38 -04:00
|
|
|
end
|
2017-10-08 14:36:45 -04:00
|
|
|
|
2017-10-10 15:15:38 -04:00
|
|
|
def process_registry_access(type, name, actions)
|
|
|
|
return unless current_user&.admin?
|
|
|
|
return unless name == 'catalog'
|
|
|
|
return unless actions == ['*']
|
2016-05-14 12:11:48 -04:00
|
|
|
|
2017-10-10 15:15:38 -04:00
|
|
|
{ type: type, name: name, actions: ['*'] }
|
2016-05-14 12:11:48 -04:00
|
|
|
end
|
|
|
|
|
2017-03-29 07:01:48 -04:00
|
|
|
def process_repository_access(type, path, actions)
|
2017-03-30 09:41:51 -04:00
|
|
|
return unless path.valid?
|
|
|
|
|
2017-03-29 07:01:48 -04:00
|
|
|
requested_project = path.repository_project
|
2017-03-23 06:41:16 -04:00
|
|
|
|
2016-05-14 12:11:48 -04:00
|
|
|
return unless requested_project
|
|
|
|
|
|
|
|
actions = actions.select do |action|
|
|
|
|
can_access?(requested_project, action)
|
|
|
|
end
|
|
|
|
|
2017-03-29 07:01:48 -04:00
|
|
|
return unless actions.present?
|
|
|
|
|
2017-03-30 09:24:46 -04:00
|
|
|
# At this point user/build is already authenticated.
|
|
|
|
#
|
|
|
|
ensure_container_repository!(path, actions)
|
|
|
|
|
2017-03-29 07:01:48 -04:00
|
|
|
{ type: type, name: path.to_s, actions: actions }
|
2016-05-14 12:11:48 -04:00
|
|
|
end
|
|
|
|
|
2017-03-30 09:24:46 -04:00
|
|
|
##
|
|
|
|
# Because we do not have two way communication with registry yet,
|
|
|
|
# we create a container repository image resource when push to the
|
|
|
|
# registry is successfuly authorized.
|
|
|
|
#
|
|
|
|
def ensure_container_repository!(path, actions)
|
|
|
|
return if path.has_repository?
|
|
|
|
return unless actions.include?('push')
|
|
|
|
|
2017-03-31 06:27:05 -04:00
|
|
|
ContainerRepository.create_from_path!(path)
|
2017-03-30 09:24:46 -04:00
|
|
|
end
|
|
|
|
|
2016-05-14 12:11:48 -04:00
|
|
|
def can_access?(requested_project, requested_action)
|
2016-05-14 15:22:45 -04:00
|
|
|
return false unless requested_project.container_registry_enabled?
|
|
|
|
|
2016-05-14 12:11:48 -04:00
|
|
|
case requested_action
|
|
|
|
when 'pull'
|
2018-04-05 09:49:18 -04:00
|
|
|
build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
|
2016-05-14 12:11:48 -04:00
|
|
|
when 'push'
|
2016-09-15 04:34:53 -04:00
|
|
|
build_can_push?(requested_project) || user_can_push?(requested_project)
|
2016-07-31 23:38:16 -04:00
|
|
|
when '*'
|
2017-08-07 05:29:34 -04:00
|
|
|
user_can_admin?(requested_project)
|
2016-05-14 12:11:48 -04:00
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def registry
|
|
|
|
Gitlab.config.registry
|
|
|
|
end
|
2016-08-08 06:01:25 -04:00
|
|
|
|
2018-04-05 09:49:18 -04:00
|
|
|
def can_user?(ability, project)
|
2018-04-06 10:30:21 -04:00
|
|
|
user = current_user.is_a?(User) ? current_user : nil
|
|
|
|
can?(user, ability, project)
|
2018-04-05 09:49:18 -04:00
|
|
|
end
|
|
|
|
|
2016-09-15 04:34:53 -04:00
|
|
|
def build_can_pull?(requested_project)
|
|
|
|
# Build can:
|
2016-09-16 06:46:33 -04:00
|
|
|
# 1. pull from its own project (for ex. a build)
|
2016-09-15 04:34:53 -04:00
|
|
|
# 2. read images from dependent projects if creator of build is a team member
|
2018-04-05 09:49:18 -04:00
|
|
|
has_authentication_ability?(:build_read_container_image) &&
|
|
|
|
(requested_project == project || can_user?(:build_read_container_image, requested_project))
|
2016-08-08 06:01:25 -04:00
|
|
|
end
|
|
|
|
|
2017-08-07 05:29:34 -04:00
|
|
|
def user_can_admin?(requested_project)
|
2017-03-16 18:19:12 -04:00
|
|
|
has_authentication_ability?(:admin_container_image) &&
|
2018-04-05 09:49:18 -04:00
|
|
|
can_user?(:admin_container_image, requested_project)
|
2017-03-16 18:19:12 -04:00
|
|
|
end
|
|
|
|
|
2016-09-15 04:34:53 -04:00
|
|
|
def user_can_pull?(requested_project)
|
2016-11-08 13:37:15 -05:00
|
|
|
has_authentication_ability?(:read_container_image) &&
|
2018-04-06 10:30:21 -04:00
|
|
|
can_user?(:read_container_image, requested_project)
|
2018-04-05 09:49:18 -04:00
|
|
|
end
|
2018-04-05 13:22:34 -04:00
|
|
|
|
2018-04-05 09:49:18 -04:00
|
|
|
def deploy_token_can_pull?(requested_project)
|
|
|
|
has_authentication_ability?(:read_container_image) &&
|
|
|
|
current_user.is_a?(DeployToken) &&
|
2018-04-10 03:31:30 -04:00
|
|
|
current_user.has_access_to?(requested_project) &&
|
|
|
|
current_user.read_registry?
|
2016-08-08 06:01:25 -04:00
|
|
|
end
|
|
|
|
|
2017-03-29 08:01:05 -04:00
|
|
|
##
|
|
|
|
# We still support legacy pipeline triggers which do not have associated
|
|
|
|
# actor. New permissions model and new triggers are always associated with
|
|
|
|
# an actor, so this should be improved in 10.0 version of GitLab.
|
|
|
|
#
|
2016-09-15 04:34:53 -04:00
|
|
|
def build_can_push?(requested_project)
|
2016-09-16 06:46:33 -04:00
|
|
|
# Build can push only to the project from which it originates
|
2016-11-08 13:37:15 -05:00
|
|
|
has_authentication_ability?(:build_create_container_image) &&
|
2016-09-15 04:34:53 -04:00
|
|
|
requested_project == project
|
2016-08-08 06:01:25 -04:00
|
|
|
end
|
|
|
|
|
2016-09-15 04:34:53 -04:00
|
|
|
def user_can_push?(requested_project)
|
2016-11-08 13:37:15 -05:00
|
|
|
has_authentication_ability?(:create_container_image) &&
|
2018-04-05 13:22:34 -04:00
|
|
|
can_user?(:create_container_image, requested_project)
|
2016-08-08 06:01:25 -04:00
|
|
|
end
|
2016-09-26 06:18:21 -04:00
|
|
|
|
|
|
|
def error(code, status:, message: '')
|
2017-03-29 08:01:05 -04:00
|
|
|
{ errors: [{ code: code, message: message }], http_status: status }
|
2016-09-26 06:18:21 -04:00
|
|
|
end
|
2016-11-08 13:37:15 -05:00
|
|
|
|
|
|
|
def has_authentication_ability?(capability)
|
2017-03-29 08:01:05 -04:00
|
|
|
@authentication_abilities.to_a.include?(capability)
|
2016-11-08 13:37:15 -05:00
|
|
|
end
|
2016-05-14 12:11:48 -04:00
|
|
|
end
|
|
|
|
end
|