af16fd687e
Use existing `public_url` validation to block various local urls. Note that this validation will allow local urls if the "Allow requests to the local network from hooks and services" admin setting is enabled. Block KubeClient from using local addresses It will also respect `allow_local_requests_from_hooks_and_services` so if that is enabled KubeClinet will allow local addresses
171 lines
4.9 KiB
Ruby
171 lines
4.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'uri'
|
|
|
|
module Gitlab
|
|
module Kubernetes
|
|
# Wrapper around Kubeclient::Client to dispatch
|
|
# the right message to the client that can respond to the message.
|
|
# We must have a kubeclient for each ApiGroup as there is no
|
|
# other way to use the Kubeclient gem.
|
|
#
|
|
# See https://github.com/abonas/kubeclient/issues/348.
|
|
class KubeClient
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
SUPPORTED_API_GROUPS = {
|
|
core: { group: 'api', version: 'v1' },
|
|
rbac: { group: 'apis/rbac.authorization.k8s.io', version: 'v1' },
|
|
extensions: { group: 'apis/extensions', version: 'v1beta1' },
|
|
knative: { group: 'apis/serving.knative.dev', version: 'v1alpha1' }
|
|
}.freeze
|
|
|
|
SUPPORTED_API_GROUPS.each do |name, params|
|
|
client_method_name = "#{name}_client".to_sym
|
|
|
|
define_method(client_method_name) do
|
|
strong_memoize(client_method_name) do
|
|
build_kubeclient(params[:group], params[:version])
|
|
end
|
|
end
|
|
end
|
|
|
|
# Core API methods delegates to the core api group client
|
|
delegate :get_pods,
|
|
:get_secrets,
|
|
:get_config_map,
|
|
:get_namespace,
|
|
:get_pod,
|
|
:get_secret,
|
|
:get_service,
|
|
:get_service_account,
|
|
:delete_pod,
|
|
:create_config_map,
|
|
:create_namespace,
|
|
:create_pod,
|
|
:create_secret,
|
|
:create_service_account,
|
|
:update_config_map,
|
|
:update_secret,
|
|
:update_service_account,
|
|
to: :core_client
|
|
|
|
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
|
|
# group client
|
|
delegate :create_cluster_role_binding,
|
|
:get_cluster_role_binding,
|
|
:update_cluster_role_binding,
|
|
to: :rbac_client
|
|
|
|
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
|
|
# group client
|
|
delegate :create_role_binding,
|
|
:get_role_binding,
|
|
:update_role_binding,
|
|
to: :rbac_client
|
|
|
|
# Deployments resource is currently on the apis/extensions api group
|
|
delegate :get_deployments,
|
|
to: :extensions_client
|
|
|
|
# non-entity methods that can only work with the core client
|
|
# as it uses the pods/log resource
|
|
delegate :get_pod_log,
|
|
:watch_pod_log,
|
|
to: :core_client
|
|
|
|
attr_reader :api_prefix, :kubeclient_options
|
|
|
|
# We disable redirects through 'http_max_redirects: 0',
|
|
# so that KubeClient does not follow redirects and
|
|
# expose internal services.
|
|
def initialize(api_prefix, **kubeclient_options)
|
|
@api_prefix = api_prefix
|
|
@kubeclient_options = kubeclient_options.merge(http_max_redirects: 0)
|
|
|
|
validate_url!
|
|
end
|
|
|
|
def create_or_update_cluster_role_binding(resource)
|
|
if cluster_role_binding_exists?(resource)
|
|
update_cluster_role_binding(resource)
|
|
else
|
|
create_cluster_role_binding(resource)
|
|
end
|
|
end
|
|
|
|
def create_or_update_role_binding(resource)
|
|
if role_binding_exists?(resource)
|
|
update_role_binding(resource)
|
|
else
|
|
create_role_binding(resource)
|
|
end
|
|
end
|
|
|
|
def create_or_update_service_account(resource)
|
|
if service_account_exists?(resource)
|
|
update_service_account(resource)
|
|
else
|
|
create_service_account(resource)
|
|
end
|
|
end
|
|
|
|
def create_or_update_secret(resource)
|
|
if secret_exists?(resource)
|
|
update_secret(resource)
|
|
else
|
|
create_secret(resource)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def validate_url!
|
|
return if Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
|
|
|
|
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
|
|
end
|
|
|
|
def cluster_role_binding_exists?(resource)
|
|
get_cluster_role_binding(resource.metadata.name)
|
|
rescue ::Kubeclient::ResourceNotFoundError
|
|
false
|
|
end
|
|
|
|
def role_binding_exists?(resource)
|
|
get_role_binding(resource.metadata.name, resource.metadata.namespace)
|
|
rescue ::Kubeclient::ResourceNotFoundError
|
|
false
|
|
end
|
|
|
|
def service_account_exists?(resource)
|
|
get_service_account(resource.metadata.name, resource.metadata.namespace)
|
|
rescue ::Kubeclient::ResourceNotFoundError
|
|
false
|
|
end
|
|
|
|
def secret_exists?(resource)
|
|
get_secret(resource.metadata.name, resource.metadata.namespace)
|
|
rescue ::Kubeclient::ResourceNotFoundError
|
|
false
|
|
end
|
|
|
|
def build_kubeclient(api_group, api_version)
|
|
::Kubeclient::Client.new(
|
|
join_api_url(api_prefix, api_group),
|
|
api_version,
|
|
**kubeclient_options
|
|
)
|
|
end
|
|
|
|
def join_api_url(api_prefix, api_path)
|
|
url = URI.parse(api_prefix)
|
|
prefix = url.path.sub(%r{/+\z}, '')
|
|
|
|
url.path = [prefix, api_path].join("/")
|
|
|
|
url.to_s
|
|
end
|
|
end
|
|
end
|
|
end
|