gitlab-org--gitlab-foss/spec/models/clusters/platforms/kubernetes_spec.rb
Thong Kuah af16fd687e Do not allow local urls in Kubernetes form
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
2019-02-21 23:16:11 +13:00

465 lines
13 KiB
Ruby

require 'spec_helper'
describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do
include KubernetesHelpers
include ReactiveCachingHelpers
it { is_expected.to belong_to(:cluster) }
it { is_expected.to be_kind_of(Gitlab::Kubernetes) }
it { is_expected.to be_kind_of(ReactiveCaching) }
it { is_expected.to respond_to :ca_pem }
it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) }
it { is_expected.to validate_presence_of(:api_url) }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to delegate_method(:project).to(:cluster) }
it { is_expected.to delegate_method(:enabled?).to(:cluster) }
it { is_expected.to delegate_method(:managed?).to(:cluster) }
it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) }
it_behaves_like 'having unique enum values'
describe 'before_validation' do
context 'when namespace includes upper case' do
let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
let(:namespace) { 'ABC' }
it 'converts to lower case' do
expect(kubernetes.namespace).to eq('abc')
end
end
end
describe 'validation' do
subject { kubernetes.valid? }
context 'when validates namespace' do
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: namespace) }
context 'when namespace is blank' do
let(:namespace) { '' }
it { is_expected.to be_truthy }
end
context 'when namespace is longer than 63' do
let(:namespace) { 'a' * 64 }
it { is_expected.to be_falsey }
end
context 'when namespace includes invalid character' do
let(:namespace) { '!!!!!!' }
it { is_expected.to be_falsey }
end
context 'when namespace is vaild' do
let(:namespace) { 'namespace-123' }
it { is_expected.to be_truthy }
end
context 'for group cluster' do
let(:namespace) { 'namespace-123' }
let(:cluster) { build(:cluster, :group, :provided_by_user) }
let(:kubernetes) { cluster.platform_kubernetes }
before do
kubernetes.namespace = namespace
end
it { is_expected.to be_falsey }
end
end
context 'when validates api_url' do
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
before do
kubernetes.api_url = api_url
end
context 'when api_url is invalid url' do
let(:api_url) { '!!!!!!' }
it { expect(kubernetes.save).to be_falsey }
end
context 'when api_url is nil' do
let(:api_url) { nil }
it { expect(kubernetes.save).to be_falsey }
end
context 'when api_url is valid url' do
let(:api_url) { 'https://111.111.111.111' }
it { expect(kubernetes.save).to be_truthy }
end
context 'when api_url is localhost' do
let(:api_url) { 'http://localhost:22' }
it { expect(kubernetes.save).to be_falsey }
context 'Application settings allows local requests' do
before do
allow(ApplicationSetting)
.to receive(:current)
.and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true))
end
it { expect(kubernetes.save).to be_truthy }
end
end
end
context 'when validates token' do
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
before do
kubernetes.token = token
end
context 'when token is nil' do
let(:token) { nil }
it { expect(kubernetes.save).to be_falsey }
end
end
context 'ca_cert' do
let(:kubernetes) { build(:cluster_platform_kubernetes, ca_pem: ca_pem) }
context 'with a valid certificate' do
let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
it { is_expected.to be_truthy }
end
context 'with an invalid certificate' do
let(:ca_pem) { "invalid" }
it { is_expected.to be_falsey }
context 'but the certificate is not being updated' do
before do
allow(kubernetes).to receive(:ca_cert_changed?).and_return(false)
end
it { is_expected.to be_truthy }
end
end
context 'with no certificate' do
let(:ca_pem) { "" }
it { is_expected.to be_truthy }
end
end
describe 'when using reserved namespaces' do
subject { build(:cluster_platform_kubernetes, namespace: namespace) }
context 'when no namespace is manually assigned' do
let(:namespace) { nil }
it { is_expected.to be_valid }
end
context 'when no reserved namespace is assigned' do
let(:namespace) { 'my-namespace' }
it { is_expected.to be_valid }
end
context 'when reserved namespace is assigned' do
let(:namespace) { 'gitlab-managed-apps' }
it { is_expected.not_to be_valid }
end
end
end
describe '#kubeclient' do
let(:cluster) { create(:cluster, :project) }
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace', cluster: cluster) }
subject { kubernetes.kubeclient }
before do
create(:cluster_kubernetes_namespace,
cluster: kubernetes.cluster,
cluster_project: kubernetes.cluster.cluster_project,
project: kubernetes.cluster.cluster_project.project)
end
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
end
describe '#rbac?' do
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
subject { kubernetes.rbac? }
it { is_expected.to be_truthy }
end
describe '#actual_namespace' do
let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
let(:platform) do
create(:cluster_platform_kubernetes,
cluster: cluster,
namespace: namespace)
end
subject { platform.actual_namespace }
context 'with a namespace assigned' do
let(:namespace) { 'namespace-123' }
it { is_expected.to eq(namespace) }
end
context 'with no namespace assigned' do
let(:namespace) { nil }
context 'when kubernetes namespace is present' do
let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
before do
kubernetes_namespace
end
it { is_expected.to eq(kubernetes_namespace.namespace) }
end
context 'when kubernetes namespace is not present' do
it { is_expected.to eq("#{project.path}-#{project.id}") }
end
end
end
describe '#predefined_variables' do
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) }
let(:api_url) { 'https://kube.domain.com' }
let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
subject { kubernetes.predefined_variables(project: cluster.project) }
shared_examples 'setting variables' do
it 'sets the variables' do
expect(subject).to include(
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_CA_PEM', value: ca_pem, public: true },
{ key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
)
end
end
context 'kubernetes namespace is created with no service account token' do
let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
it_behaves_like 'setting variables'
it 'sets KUBE_TOKEN' do
expect(subject).to include(
{ key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
)
end
end
context 'kubernetes namespace is created with no service account token' do
let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) }
it_behaves_like 'setting variables'
it 'sets KUBE_TOKEN' do
expect(subject).to include(
{ key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
)
end
end
context 'namespace is provided' do
let(:namespace) { 'my-project' }
before do
kubernetes.namespace = namespace
end
it_behaves_like 'setting variables'
it 'sets KUBE_TOKEN' do
expect(subject).to include(
{ key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
)
end
end
context 'no namespace provided' do
let(:namespace) { kubernetes.actual_namespace }
it_behaves_like 'setting variables'
it 'sets KUBE_TOKEN' do
expect(subject).to include(
{ key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
)
end
end
context 'group level cluster' do
let!(:cluster) { create(:cluster, :group, platform_kubernetes: kubernetes) }
let(:project) { create(:project, group: cluster.group) }
subject { kubernetes.predefined_variables(project: project) }
context 'no kubernetes namespace for the project' do
it_behaves_like 'setting variables'
it 'does not return KUBE_TOKEN' do
expect(subject).not_to include(
{ key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
)
end
end
context 'kubernetes namespace exists for the project' do
let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: project) }
it_behaves_like 'setting variables'
it 'sets KUBE_TOKEN' do
expect(subject).to include(
{ key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
)
end
end
end
context 'with a domain' do
let!(:cluster) do
create(:cluster, :provided_by_gcp, :with_domain,
platform_kubernetes: kubernetes)
end
it 'sets KUBE_INGRESS_BASE_DOMAIN' do
expect(subject).to include(
{ key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true }
)
end
end
end
describe '#terminals' do
subject { service.terminals(environment) }
let!(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
let(:project) { cluster.project }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
context 'with invalid pods' do
it 'returns no terminals' do
stub_reactive_cache(service, pods: [{ "bad" => "pod" }])
is_expected.to be_empty
end
end
context 'with valid pods' do
let(:pod) { kube_pod(app: environment.slug) }
let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")]
)
end
it 'returns terminals' do
is_expected.to eq(terminals + terminals)
end
it 'uses max session time from settings' do
stub_application_setting(terminal_max_session_time: 600)
times = subject.map { |terminal| terminal[:max_session_time] }
expect(times).to eq [600, 600, 600, 600]
end
end
end
describe '#calculate_reactive_cache' do
subject { service.calculate_reactive_cache }
let!(:cluster) { create(:cluster, :project, enabled: enabled, platform_kubernetes: service) }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
let(:enabled) { true }
context 'when cluster is disabled' do
let(:enabled) { false }
it { is_expected.to be_nil }
end
context 'when kubernetes responds with valid pods and deployments' do
before do
stub_kubeclient_pods
stub_kubeclient_deployments
end
it { is_expected.to include(pods: [kube_pod]) }
end
context 'when kubernetes responds with 500s' do
before do
stub_kubeclient_pods(status: 500)
stub_kubeclient_deployments(status: 500)
end
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
end
context 'when kubernetes responds with 404s' do
before do
stub_kubeclient_pods(status: 404)
stub_kubeclient_deployments(status: 404)
end
it { is_expected.to include(pods: []) }
end
end
describe '#update_kubernetes_namespace' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
let(:platform) { cluster.platform }
context 'when namespace is updated' do
it 'should call ConfigureWorker' do
expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once
platform.namespace = 'new-namespace'
platform.save
end
end
context 'when namespace is not updated' do
it 'should not call ConfigureWorker' do
expect(ClusterConfigureWorker).not_to receive(:perform_async)
platform.username = "new-username"
platform.save
end
end
end
end