Skip to content

Commit

Permalink
fix docker cli authz dance
Browse files Browse the repository at this point in the history
  • Loading branch information
ezekg committed Jan 23, 2025
1 parent 3c496e2 commit 7b64d0a
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 59 deletions.
20 changes: 15 additions & 5 deletions app/controllers/api/v1/release_engines/oci/blobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Oci::BlobsController < Api::V1::BaseController
def show
authorize! package

descriptor = authorized_scope(package.descriptors).find_by!(
descriptor = package.descriptors.find_by!(
content_digest: params[:digest],
)
authorize! descriptor
Expand All @@ -23,6 +23,15 @@ def show
redirect_to vanity_v1_account_release_artifact_url(current_account, descriptor.artifact, filename: descriptor.content_path, host: request.host),
status: :see_other
end
rescue ActionPolicy::Unauthorized
# FIXME(ezekg) docker expects a 401 Unauthorized response with an WWW-Authenticate
# challenge, so unfortunately, we can't return a 404 here like we
# usually do for unauthorized requests (so as not to leak data).
if current_bearer.nil?
render_unauthorized(code: 'UNAUTHORIZED')
else
render_forbidden(code: 'DENIED')
end
end

private
Expand All @@ -32,10 +41,11 @@ def show
def require_ee! = super(entitlements: %i[oci_engine])

def set_package
scoped_packages = authorized_scope(current_account.release_packages.oci)
.where_assoc_exists(
:descriptors, # must exist
)
# NOTE(ezekg) see above comment i.r.t. docker authentication on why we're
# skipping authorized_scope here and elsewhere
scoped_packages = current_account.release_packages.oci.where_assoc_exists(
:descriptors, # must exist
)

@package = Current.resource = FindByAliasService.call(
scoped_packages,
Expand Down
20 changes: 15 additions & 5 deletions app/controllers/api/v1/release_engines/oci/manifests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Oci::ManifestsController < Api::V1::BaseController
def show
authorize! package

manifest = authorized_scope(package.manifests).find_by_reference!(params[:reference],
manifest = package.manifests.find_by_reference!(params[:reference],
accepts: request.accepts.collect(&:to_s),
prefers: %w[
application/vnd.oci.image.index.v1+json
Expand All @@ -35,6 +35,15 @@ def show
else
render body: manifest.content
end
rescue ActionPolicy::Unauthorized
# FIXME(ezekg) docker expects a 401 Unauthorized response with an WWW-Authenticate
# challenge, so unfortunately, we can't return a 404 here like we
# usually do for unauthorized requests (so as not to leak data).
if current_bearer.nil?
render_unauthorized(code: 'UNAUTHORIZED')
else
render_forbidden(code: 'DENIED')
end
end

private
Expand All @@ -44,10 +53,11 @@ def show
def require_ee! = super(entitlements: %i[oci_engine])

def set_package
scoped_packages = authorized_scope(current_account.release_packages.oci)
.where_assoc_exists(
:manifests, # must exist
)
# NOTE(ezekg) see above comment i.r.t. docker authentication on why we're
# skipping authorized_scope here and elsewhere
scoped_packages = current_account.release_packages.oci.where_assoc_exists(
:manifests, # must exist
)

@package = Current.resource = FindByAliasService.call(
scoped_packages,
Expand Down
13 changes: 12 additions & 1 deletion app/controllers/api/v1/release_engines/oci/tags_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ def index
name: package.key,
tags:,
}
rescue ActionPolicy::Unauthorized
# FIXME(ezekg) docker expects a 401 Unauthorized response with an WWW-Authenticate
# challenge, so unfortunately, we can't return a 404 here like we
# usually do for unauthorized requests (so as not to leak data).
if current_bearer.nil?
render_unauthorized(code: 'UNAUTHORIZED')
else
render_forbidden(code: 'DENIED')
end
end

private
Expand All @@ -31,7 +40,9 @@ def index
def require_ee! = super(entitlements: %i[oci_engine])

def set_package
scoped_packages = authorized_scope(current_account.release_packages.oci)
# NOTE(ezekg) see above comment i.r.t. docker authentication on why we're
# skipping authorized_scope here
scoped_packages = current_account.release_packages.oci

@package = Current.resource = FindByAliasService.call(
scoped_packages,
Expand Down
13 changes: 12 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,16 @@ def render_forbidden(**kwargs)
def render_unauthorized(**kwargs)
skip_verify_authorized!

self.headers['WWW-Authenticate'] = %(Bearer realm="keygen")
# FIXME(ezekg) docker wants to do a jwt token dance unless we stick to a basic
# auth scheme (which is fine since our basic auth scheme only
# supports license keys and tokens, not passwords!)
default_challenge_scheme = oci? ? 'Basic' : 'Bearer'

challenge_scheme = authentication_scheme&.capitalize || default_challenge_scheme
challenge_realm = 'keygen'
challenge = %(#{challenge_scheme} realm="#{challenge_realm}")

response.headers['WWW-Authenticate'] = challenge

respond_to do |format|
format.any {
Expand Down Expand Up @@ -567,6 +576,8 @@ def rescue_from_exceptions
)
end

def oci? = request.subdomain == 'oci.pkg'

def prefers?(preference)
preferences = request.headers.fetch('Prefer') { request.query_parameters.fetch(:prefer, '').to_s }
.split(',')
Expand Down
4 changes: 4 additions & 0 deletions app/models/release_package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ class ReleasePackage < ApplicationRecord
to: :engine,
allow_nil: true

delegate :open?, :closed?, :licensed?,
to: :product,
allow_nil: true

def engine_id? = release_engine_id?
def engine_id = release_engine_id
def engine_id=(id)
Expand Down
8 changes: 4 additions & 4 deletions app/policies/product_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ def index?
case bearer
in role: Role(:admin | :developer | :sales_agent | :support_agent | :read_only | :environment)
allow!
in role: Role(:user) if record.all? { _1.open? || _1.id.in?(bearer.product_ids) }
in role: Role(:user) if record.all? { _1.open? || _1.licensed? && _1.id.in?(bearer.product_ids) }
allow!
in role: Role(:license) if record.all? { _1.open? || _1 == bearer.product }
in role: Role(:license) if record.all? { _1.open? || _1.licensed? && _1 == bearer.product }
allow!
else
deny!
Expand All @@ -50,9 +50,9 @@ def show?
allow!
in role: Role(:product) if record == bearer
allow!
in role: Role(:user) if record.open? || bearer.products.exists?(record.id)
in role: Role(:user) if record.open? || record.licensed? && bearer.products.exists?(record.id)
allow!
in role: Role(:license) if record.open? || record == bearer.product
in role: Role(:license) if record.open? || record.licensed? && record == bearer.product
allow!
else
deny!
Expand Down
10 changes: 5 additions & 5 deletions app/policies/release_package_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ def index?
allow!
in role: Role(:product) if record.all? { _1.product == bearer }
allow!
in role: Role(:user) if record.all? { _1.product.open? || _1.product_id.in?(bearer.product_ids) }
in role: Role(:user) if record.all? { _1.open? || _1.licensed? && _1.product_id.in?(bearer.product_ids) }
allow!
in role: Role(:license) if record.all? { _1.product.open? || _1.product == bearer.product }
in role: Role(:license) if record.all? { _1.open? || _1.licensed? && _1.product == bearer.product }
allow!
else
record.all? { _1.product.open? }
record.all? { _1.open? }
end
end

Expand All @@ -54,9 +54,9 @@ def show?
allow!
in role: Role(:product) if record.product == bearer
allow!
in role: Role(:user) if record.product.open? || bearer.products.exists?(record.product_id)
in role: Role(:user) if record.open? || record.licensed? && bearer.products.exists?(record.product_id)
allow!
in role: Role(:license) if record.product.open? || record.product == bearer.product
in role: Role(:license) if record.open? || record.licensed? && record.product == bearer.product
allow!
else
record.product.open?
Expand Down
Loading

0 comments on commit 7b64d0a

Please sign in to comment.