210 lines
7.6 KiB
Ruby
210 lines
7.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module API
|
|
# Kubernetes Internal API
|
|
module Internal
|
|
class Kubernetes < ::API::Base
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
before do
|
|
check_feature_enabled
|
|
authenticate_gitlab_kas_request!
|
|
end
|
|
|
|
helpers do
|
|
def authenticate_gitlab_kas_request!
|
|
render_api_error!('KAS JWT authentication invalid', 401) unless Gitlab::Kas.verify_api_request(headers)
|
|
end
|
|
|
|
def agent_token
|
|
@agent_token ||= cluster_agent_token_from_authorization_token
|
|
end
|
|
|
|
def agent
|
|
@agent ||= agent_token.agent
|
|
end
|
|
|
|
def repo_type
|
|
Gitlab::GlRepository::PROJECT
|
|
end
|
|
|
|
def gitaly_info(project)
|
|
shard = repo_type.repository_for(project).shard
|
|
{
|
|
address: Gitlab::GitalyClient.address(shard),
|
|
token: Gitlab::GitalyClient.token(shard),
|
|
features: Feature::Gitaly.server_feature_flags
|
|
}
|
|
end
|
|
|
|
def gitaly_repository(project)
|
|
{
|
|
storage_name: project.repository_storage,
|
|
relative_path: project.disk_path + '.git',
|
|
gl_repository: repo_type.identifier_for_container(project),
|
|
gl_project_path: repo_type.repository_for(project).full_path
|
|
}
|
|
end
|
|
|
|
def check_feature_enabled
|
|
not_found!('Internal API not found') unless Feature.enabled?(:kubernetes_agent_internal_api, type: :ops)
|
|
end
|
|
|
|
def check_agent_token
|
|
unauthorized! unless agent_token
|
|
|
|
::Clusters::AgentTokens::TrackUsageService.new(agent_token).execute
|
|
end
|
|
|
|
def agent_has_access_to_project?(project)
|
|
Guest.can?(:download_code, project) || agent.has_access_to?(project)
|
|
end
|
|
|
|
def increment_unique_events
|
|
events = params[:unique_counters]&.slice(:agent_users_using_ci_tunnel)
|
|
|
|
events&.each do |event, entity_ids|
|
|
increment_unique_values(event, entity_ids)
|
|
end
|
|
end
|
|
|
|
def increment_count_events
|
|
events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request)
|
|
|
|
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events)
|
|
end
|
|
end
|
|
|
|
namespace 'internal' do
|
|
namespace 'kubernetes' do
|
|
before do
|
|
check_agent_token
|
|
end
|
|
|
|
desc 'Gets agent info' do
|
|
detail 'Retrieves agent info for the given token'
|
|
end
|
|
route_setting :authentication, cluster_agent_token_allowed: true
|
|
get '/agent_info', feature_category: :deployment_management, urgency: :low do
|
|
project = agent.project
|
|
|
|
status 200
|
|
{
|
|
project_id: project.id,
|
|
agent_id: agent.id,
|
|
agent_name: agent.name,
|
|
gitaly_info: gitaly_info(project),
|
|
gitaly_repository: gitaly_repository(project),
|
|
default_branch: project.default_branch_or_main
|
|
}
|
|
end
|
|
|
|
desc 'Gets project info' do
|
|
detail 'Retrieves project info (if authorized)'
|
|
end
|
|
route_setting :authentication, cluster_agent_token_allowed: true
|
|
get '/project_info', feature_category: :deployment_management, urgency: :low do
|
|
project = find_project(params[:id])
|
|
|
|
not_found! unless agent_has_access_to_project?(project)
|
|
|
|
status 200
|
|
{
|
|
project_id: project.id,
|
|
gitaly_info: gitaly_info(project),
|
|
gitaly_repository: gitaly_repository(project),
|
|
default_branch: project.default_branch_or_main
|
|
}
|
|
end
|
|
end
|
|
|
|
namespace 'kubernetes/agent_configuration' do
|
|
desc 'POST agent configuration' do
|
|
detail 'Store configuration for an agent'
|
|
end
|
|
params do
|
|
requires :agent_id, type: Integer, desc: 'ID of the configured Agent'
|
|
requires :agent_config, type: JSON, desc: 'Configuration for the Agent'
|
|
end
|
|
post '/', feature_category: :deployment_management, urgency: :low do
|
|
agent = ::Clusters::Agent.find(params[:agent_id])
|
|
|
|
::Clusters::Agents::Authorizations::CiAccess::RefreshService.new(agent, config: params[:agent_config]).execute
|
|
::Clusters::Agents::Authorizations::UserAccess::RefreshService.new(agent, config: params[:agent_config]).execute
|
|
|
|
no_content!
|
|
end
|
|
end
|
|
|
|
namespace 'kubernetes/authorize_proxy_user' do
|
|
desc 'Authorize a proxy user request'
|
|
params do
|
|
requires :agent_id, type: Integer, desc: 'ID of the agent accessed'
|
|
requires :access_type, type: String, values: ['session_cookie'], desc: 'The type of the access key being verified.'
|
|
requires :access_key, type: String, desc: 'The authentication secret for the given access type.'
|
|
given access_type: ->(val) { val == 'session_cookie' } do
|
|
requires :csrf_token, type: String, allow_blank: false, desc: 'CSRF token that must be checked when access_type is "session_cookie", to ensure the request originates from a GitLab browsing session.'
|
|
end
|
|
end
|
|
post '/', feature_category: :deployment_management do
|
|
# Load session
|
|
public_session_id_string =
|
|
begin
|
|
Gitlab::Kas::UserAccess.decrypt_public_session_id(params[:access_key])
|
|
rescue StandardError
|
|
bad_request!('Invalid access_key')
|
|
end
|
|
|
|
session_id = Rack::Session::SessionId.new(public_session_id_string)
|
|
session = ActiveSession.sessions_from_ids([session_id.private_id]).first
|
|
unauthorized!('Invalid session') unless session
|
|
|
|
# CSRF check
|
|
unless ::Gitlab::Kas::UserAccess.valid_authenticity_token?(session.symbolize_keys, params[:csrf_token])
|
|
unauthorized!('CSRF token does not match')
|
|
end
|
|
|
|
# Load user
|
|
user = Warden::SessionSerializer.new('rack.session' => session).fetch(:user)
|
|
unauthorized!('Invalid user in session') unless user
|
|
|
|
# Load agent
|
|
agent = ::Clusters::Agent.find(params[:agent_id])
|
|
unauthorized!('Feature disabled for agent') unless ::Gitlab::Kas::UserAccess.enabled_for?(agent)
|
|
|
|
service_response = ::Clusters::Agents::AuthorizeProxyUserService.new(user, agent).execute
|
|
render_api_error!(service_response[:message], service_response[:reason]) unless service_response.success?
|
|
|
|
service_response.payload
|
|
end
|
|
end
|
|
|
|
namespace 'kubernetes/usage_metrics' do
|
|
desc 'POST usage metrics' do
|
|
detail 'Updates usage metrics for agent'
|
|
end
|
|
params do
|
|
optional :counters, type: Hash do
|
|
optional :gitops_sync, type: Integer, desc: 'The count to increment the gitops_sync metric by'
|
|
optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request metric by'
|
|
end
|
|
|
|
optional :unique_counters, type: Hash do
|
|
optional :agent_users_using_ci_tunnel, type: Array[Integer], desc: 'An array of user ids that have interacted with CI Tunnel'
|
|
end
|
|
end
|
|
post '/', feature_category: :deployment_management do
|
|
increment_count_events
|
|
increment_unique_events
|
|
|
|
no_content!
|
|
rescue ArgumentError => e
|
|
bad_request!(e.message)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
API::Internal::Kubernetes.prepend_mod_with('API::Internal::Kubernetes')
|