Commit 1408c633 authored by Alex Ives's avatar Alex Ives Committed by Matt Kasa

Add RBAC permissions for getting knative version

- Add RBAC permission to list deployments in knative-serving namespace
- Ensure service account has new permission
- Update documentation to instruct people to add the ability to list
  deployments in knative-serving
- Add get_cluster_role_bindings delegation to kubeclient
- Add KnativeVersionRoleBindingFinder
- Add knative_version_role_binding to KubernetesNamespace prereq
- Update tests for knative version role and binding

Relates to https://gitlab.com/gitlab-org/gitlabktl/issues/17Co-Authored-By: default avatarMatt Kasa <mkasa@gitlab.com>
parent 158da98e
# frozen_string_literal: true
module Clusters
class KnativeVersionRoleBindingFinder
attr_reader :cluster
def initialize(cluster)
@cluster = cluster
end
def execute
cluster&.kubeclient&.get_cluster_role_bindings&.find do |resource|
resource.metadata.name == Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME
end
end
end
end
...@@ -49,8 +49,14 @@ module Clusters ...@@ -49,8 +49,14 @@ module Clusters
create_or_update_knative_serving_role create_or_update_knative_serving_role
create_or_update_knative_serving_role_binding create_or_update_knative_serving_role_binding
create_or_update_crossplane_database_role create_or_update_crossplane_database_role
create_or_update_crossplane_database_role_binding create_or_update_crossplane_database_role_binding
return unless knative_serving_namespace
create_or_update_knative_version_role
create_or_update_knative_version_role_binding
end end
private private
...@@ -64,6 +70,12 @@ module Clusters ...@@ -64,6 +70,12 @@ module Clusters
).ensure_exists! ).ensure_exists!
end end
def knative_serving_namespace
kubeclient.core_client.get_namespaces.find do |namespace|
namespace.metadata.name == Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE
end
end
def create_role_or_cluster_role_binding def create_role_or_cluster_role_binding
if namespace_creator if namespace_creator
kubeclient.create_or_update_role_binding(role_binding_resource) kubeclient.create_or_update_role_binding(role_binding_resource)
...@@ -88,6 +100,14 @@ module Clusters ...@@ -88,6 +100,14 @@ module Clusters
kubeclient.update_role_binding(crossplane_database_role_binding_resource) kubeclient.update_role_binding(crossplane_database_role_binding_resource)
end end
def create_or_update_knative_version_role
kubeclient.update_cluster_role(knative_version_role_resource)
end
def create_or_update_knative_version_role_binding
kubeclient.update_cluster_role_binding(knative_version_role_binding_resource)
end
def service_account_resource def service_account_resource
Gitlab::Kubernetes::ServiceAccount.new( Gitlab::Kubernetes::ServiceAccount.new(
service_account_name, service_account_name,
...@@ -166,6 +186,27 @@ module Clusters ...@@ -166,6 +186,27 @@ module Clusters
service_account_name: service_account_name service_account_name: service_account_name
).generate ).generate
end end
def knative_version_role_resource
Gitlab::Kubernetes::ClusterRole.new(
name: Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME,
rules: [{
apiGroups: %w(apps),
resources: %w(deployments),
verbs: %w(list get)
}]
).generate
end
def knative_version_role_binding_resource
subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
Gitlab::Kubernetes::ClusterRoleBinding.new(
Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME,
Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME,
subjects
).generate
end
end end
end end
end end
...@@ -12,5 +12,8 @@ module Clusters ...@@ -12,5 +12,8 @@ module Clusters
GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding' GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role' GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role'
GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding' GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding'
GITLAB_KNATIVE_VERSION_ROLE_NAME = 'gitlab-knative-version-role'
GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME = 'gitlab-knative-version-rolebinding'
KNATIVE_SERVING_NAMESPACE = 'knative-serving'
end end
end end
---
title: Add rbac access to knative-serving namespace deployments to get knative version information
merge_request: 20244
author:
type: changed
...@@ -116,7 +116,8 @@ You must do the following: ...@@ -116,7 +116,8 @@ You must do the following:
1. Ensure GitLab can manage Knative: 1. Ensure GitLab can manage Knative:
- For a non-GitLab managed cluster, ensure that the service account for the token - For a non-GitLab managed cluster, ensure that the service account for the token
provided can manage resources in the `serving.knative.dev` API group. provided can manage resources in the `serving.knative.dev` API group. It will also
need list access to the deployments in the `knative-serving` namespace.
- For a GitLab managed cluster, if you added the cluster in [GitLab 12.1 or later](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/30235), - For a GitLab managed cluster, if you added the cluster in [GitLab 12.1 or later](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/30235),
then GitLab will already have the required access and you can proceed to the next step. then GitLab will already have the required access and you can proceed to the next step.
...@@ -153,6 +154,19 @@ You must do the following: ...@@ -153,6 +154,19 @@ You must do the following:
- delete - delete
- patch - patch
- watch - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitlab-knative-version-role
rules:
- apiGroups:
- apps
resources:
- deployments
verbs:
- list
- get
``` ```
Then run the following command: Then run the following command:
......
...@@ -8,7 +8,7 @@ module Gitlab ...@@ -8,7 +8,7 @@ module Gitlab
def unmet? def unmet?
deployment_cluster.present? && deployment_cluster.present? &&
deployment_cluster.managed? && deployment_cluster.managed? &&
missing_namespace? (missing_namespace? || missing_knative_version_role_binding?)
end end
def complete! def complete!
...@@ -23,6 +23,10 @@ module Gitlab ...@@ -23,6 +23,10 @@ module Gitlab
kubernetes_namespace.nil? || kubernetes_namespace.service_account_token.blank? kubernetes_namespace.nil? || kubernetes_namespace.service_account_token.blank?
end end
def missing_knative_version_role_binding?
knative_version_role_binding.nil?
end
def deployment_cluster def deployment_cluster
build.deployment&.cluster build.deployment&.cluster
end end
...@@ -31,6 +35,14 @@ module Gitlab ...@@ -31,6 +35,14 @@ module Gitlab
build.deployment.environment build.deployment.environment
end end
def knative_version_role_binding
strong_memoize(:knative_version_role_binding) do
Clusters::KnativeVersionRoleBindingFinder.new(
deployment_cluster
).execute
end
end
def kubernetes_namespace def kubernetes_namespace
strong_memoize(:kubernetes_namespace) do strong_memoize(:kubernetes_namespace) do
Clusters::KubernetesNamespaceFinder.new( Clusters::KubernetesNamespaceFinder.new(
......
# frozen_string_literal: true
module Gitlab
module Kubernetes
class ClusterRole
attr_reader :name, :rules
def initialize(name:, rules:)
@name = name
@rules = rules
end
def generate
::Kubeclient::Resource.new(
metadata: metadata,
rules: rules
)
end
private
def metadata
{
name: name
}
end
end
end
end
...@@ -56,6 +56,7 @@ module Gitlab ...@@ -56,6 +56,7 @@ module Gitlab
# group client # group client
delegate :create_cluster_role_binding, delegate :create_cluster_role_binding,
:get_cluster_role_binding, :get_cluster_role_binding,
:get_cluster_role_bindings,
:update_cluster_role_binding, :update_cluster_role_binding,
to: :rbac_client to: :rbac_client
...@@ -66,6 +67,13 @@ module Gitlab ...@@ -66,6 +67,13 @@ module Gitlab
:update_role, :update_role,
to: :rbac_client to: :rbac_client
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
# group client
delegate :create_cluster_role,
:get_cluster_role,
:update_cluster_role,
to: :rbac_client
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api # RBAC methods delegates to the apis/rbac.authorization.k8s.io api
# group client # group client
delegate :create_role_binding, delegate :create_role_binding,
......
...@@ -38,13 +38,29 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do ...@@ -38,13 +38,29 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
.and_return(double(execute: kubernetes_namespace)) .and_return(double(execute: kubernetes_namespace))
end end
it { is_expected.to be_falsey } context 'and the knative version role binding is missing' do
before do
context 'and the service_account_token is blank' do allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new)
let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) } .and_return(double(execute: nil))
end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
context 'and the knative version role binding already exists' do
before do
allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new)
.and_return(double(execute: true))
end
it { is_expected.to be_falsey }
context 'and the service_account_token is blank' do
let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) }
it { is_expected.to be_truthy }
end
end
end end
end end
...@@ -115,6 +131,24 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do ...@@ -115,6 +131,24 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
subject subject
end end
end end
context 'knative version role binding is missing' do
before do
allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
.and_return(double(execute: kubernetes_namespace))
allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new)
.and_return(double(execute: nil))
end
it 'creates the knative version role binding' do
expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService)
.to receive(:new)
.with(cluster: cluster, kubernetes_namespace: kubernetes_namespace)
.and_return(service)
subject
end
end
end end
context 'completion is not required' do context 'completion is not required' do
......
...@@ -22,7 +22,7 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do ...@@ -22,7 +22,7 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
before do before do
stub_kubeclient_discover(api_url) stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url) stub_kubeclient_get_namespaces(api_url)
stub_kubeclient_get_service_account_error(api_url, 'gitlab') stub_kubeclient_get_service_account_error(api_url, 'gitlab')
stub_kubeclient_create_service_account(api_url) stub_kubeclient_create_service_account(api_url)
stub_kubeclient_get_secret_error(api_url, 'gitlab-token') stub_kubeclient_get_secret_error(api_url, 'gitlab-token')
...@@ -39,6 +39,8 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do ...@@ -39,6 +39,8 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace)
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace)
stub_kubeclient_put_cluster_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME)
stub_kubeclient_put_cluster_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME)
stub_kubeclient_get_secret( stub_kubeclient_get_secret(
api_url, api_url,
......
...@@ -141,12 +141,15 @@ describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do ...@@ -141,12 +141,15 @@ describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
before do before do
cluster.platform_kubernetes.rbac! cluster.platform_kubernetes.rbac!
stub_kubeclient_get_namespaces(api_url)
stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace) stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_create_role_binding(api_url, namespace: namespace) stub_kubeclient_create_role_binding(api_url, namespace: namespace)
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace)
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace)
stub_kubeclient_put_cluster_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME)
stub_kubeclient_put_cluster_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME)
end end
it_behaves_like 'creates service account and token' it_behaves_like 'creates service account and token'
...@@ -234,6 +237,30 @@ describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do ...@@ -234,6 +237,30 @@ describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
) )
) )
end end
it 'creates a role and role binding granting the ability to get the version of deployments in knative-serving namespace' do
subject
expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME}").with(
body: hash_including(
metadata: {
name: Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME
},
roleRef: {
apiGroup: "rbac.authorization.k8s.io",
kind: "ClusterRole",
name: Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME
},
subjects: [
{
kind: "ServiceAccount",
name: service_account_name,
namespace: namespace
}
]
)
)
end
end end
end end
end end
...@@ -194,6 +194,11 @@ module KubernetesHelpers ...@@ -194,6 +194,11 @@ module KubernetesHelpers
.to_return(kube_response({})) .to_return(kube_response({}))
end end
def stub_kubeclient_put_cluster_role_binding(api_url, name)
WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{name}")
.to_return(kube_response({}))
end
def stub_kubeclient_get_role_binding(api_url, name, namespace: 'default') def stub_kubeclient_get_role_binding(api_url, name, namespace: 'default')
WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}") WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
.to_return(kube_response({})) .to_return(kube_response({}))
...@@ -219,11 +224,21 @@ module KubernetesHelpers ...@@ -219,11 +224,21 @@ module KubernetesHelpers
.to_return(kube_response({})) .to_return(kube_response({}))
end end
def stub_kubeclient_get_namespaces(api_url)
WebMock.stub_request(:get, api_url + '/api/v1/namespaces')
.to_return(kube_response(kube_v1_namespace_list_body))
end
def stub_kubeclient_get_namespace(api_url, namespace: 'default') def stub_kubeclient_get_namespace(api_url, namespace: 'default')
WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}") WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}")
.to_return(kube_response({})) .to_return(kube_response({}))
end end
def stub_kubeclient_put_cluster_role(api_url, name)
WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterroles/#{name}")
.to_return(kube_response({}))
end
def stub_kubeclient_put_role(api_url, name, namespace: 'default') def stub_kubeclient_put_role(api_url, name, namespace: 'default')
WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}") WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}")
.to_return(kube_response({})) .to_return(kube_response({}))
...@@ -257,6 +272,20 @@ module KubernetesHelpers ...@@ -257,6 +272,20 @@ module KubernetesHelpers
} }
end end
def kube_v1_namespace_list_body
{
"kind" => "NamespaceList",
"apiVersion" => "v1",
"items" => [
{
"metadata" => {
"name" => "knative-serving"
}
}
]
}
end
def kube_v1beta1_discovery_body def kube_v1beta1_discovery_body
{ {
"kind" => "APIResourceList", "kind" => "APIResourceList",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment