Give Knative serving permissions to service account
GitLab uses a kubernetes service account to perform deployments. For serverless deployments to work as expected with externally created clusters with their own knative installations (e.g. via Cloud Run), this account requires additional permissions in the serving.knative.dev API group.
This commit is contained in:
parent
cc3ef63572
commit
6971fd261d
13 changed files with 161 additions and 15 deletions
|
@ -9,6 +9,8 @@ module Clusters
|
||||||
GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
|
GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
|
||||||
GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
|
GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
|
||||||
PROJECT_CLUSTER_ROLE_NAME = 'edit'
|
PROJECT_CLUSTER_ROLE_NAME = 'edit'
|
||||||
|
GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role'
|
||||||
|
GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,7 +41,15 @@ module Clusters
|
||||||
|
|
||||||
kubeclient.create_or_update_service_account(service_account_resource)
|
kubeclient.create_or_update_service_account(service_account_resource)
|
||||||
kubeclient.create_or_update_secret(service_account_token_resource)
|
kubeclient.create_or_update_secret(service_account_token_resource)
|
||||||
create_role_or_cluster_role_binding if rbac
|
|
||||||
|
return unless rbac
|
||||||
|
|
||||||
|
create_role_or_cluster_role_binding
|
||||||
|
|
||||||
|
return unless namespace_creator
|
||||||
|
|
||||||
|
create_or_update_knative_serving_role
|
||||||
|
create_or_update_knative_serving_role_binding
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -63,6 +71,14 @@ module Clusters
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_or_update_knative_serving_role
|
||||||
|
kubeclient.update_role(knative_serving_role_resource)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_or_update_knative_serving_role_binding
|
||||||
|
kubeclient.update_role_binding(knative_serving_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,
|
||||||
|
@ -92,6 +108,29 @@ module Clusters
|
||||||
Gitlab::Kubernetes::RoleBinding.new(
|
Gitlab::Kubernetes::RoleBinding.new(
|
||||||
name: role_binding_name,
|
name: role_binding_name,
|
||||||
role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
|
role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
|
||||||
|
role_kind: :ClusterRole,
|
||||||
|
namespace: service_account_namespace,
|
||||||
|
service_account_name: service_account_name
|
||||||
|
).generate
|
||||||
|
end
|
||||||
|
|
||||||
|
def knative_serving_role_resource
|
||||||
|
Gitlab::Kubernetes::Role.new(
|
||||||
|
name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
|
||||||
|
namespace: service_account_namespace,
|
||||||
|
rules: [{
|
||||||
|
apiGroups: %w(serving.knative.dev),
|
||||||
|
resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services),
|
||||||
|
verbs: %w(get list create update delete patch watch)
|
||||||
|
}]
|
||||||
|
).generate
|
||||||
|
end
|
||||||
|
|
||||||
|
def knative_serving_role_binding_resource
|
||||||
|
Gitlab::Kubernetes::RoleBinding.new(
|
||||||
|
name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME,
|
||||||
|
role_name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
|
||||||
|
role_kind: :Role,
|
||||||
namespace: service_account_namespace,
|
namespace: service_account_namespace,
|
||||||
service_account_name: service_account_name
|
service_account_name: service_account_name
|
||||||
).generate
|
).generate
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Create Knative role and binding with service account
|
||||||
|
merge_request: 30235
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -102,12 +102,15 @@ 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.
|
||||||
- For a GitLab managed cluster,
|
- For a GitLab managed cluster, if you added the cluster in [GitLab 12.1 or later](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30235),
|
||||||
GitLab uses a service account with the `edit` cluster role. This account needs
|
then GitLab will already have the required access and you can proceed to the next step.
|
||||||
the ability to manage resources in the `serving.knative.dev` API group.
|
|
||||||
We suggest you do this with an [aggregated ClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles)
|
Otherwise, you need to manually grant GitLab's service account the ability to manage
|
||||||
adding rules to the default `edit` cluster role:
|
resources in the `serving.knative.dev` API group. Since every GitLab service account
|
||||||
First, save the following YAML as `knative-serving-only-role.yaml`:
|
has the `edit` cluster role, the simplest way to do this is with an
|
||||||
|
[aggregated ClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles)
|
||||||
|
adding rules to the default `edit` cluster role: First, save the following YAML as
|
||||||
|
`knative-serving-only-role.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
@ -143,6 +146,9 @@ You must do the following:
|
||||||
kubectl apply -f knative-serving-only-role.yaml
|
kubectl apply -f knative-serving-only-role.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you would rather grant permissions on a per service account basis, you can do this
|
||||||
|
using a `Role` and `RoleBinding` specific to the service account and namespace.
|
||||||
|
|
||||||
1. Follow the steps to deploy [functions](#deploying-functions)
|
1. Follow the steps to deploy [functions](#deploying-functions)
|
||||||
or [serverless applications](#deploying-serverless-applications) onto your
|
or [serverless applications](#deploying-serverless-applications) onto your
|
||||||
cluster.
|
cluster.
|
||||||
|
|
|
@ -57,6 +57,13 @@ module Gitlab
|
||||||
:update_cluster_role_binding,
|
:update_cluster_role_binding,
|
||||||
to: :rbac_client
|
to: :rbac_client
|
||||||
|
|
||||||
|
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
|
||||||
|
# group client
|
||||||
|
delegate :create_role,
|
||||||
|
:get_role,
|
||||||
|
:update_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,
|
||||||
|
|
24
lib/gitlab/kubernetes/role.rb
Normal file
24
lib/gitlab/kubernetes/role.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Kubernetes
|
||||||
|
class Role
|
||||||
|
def initialize(name:, namespace:, rules:)
|
||||||
|
@name = name
|
||||||
|
@namespace = namespace
|
||||||
|
@rules = rules
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate
|
||||||
|
::Kubeclient::Resource.new(
|
||||||
|
metadata: { name: name, namespace: namespace },
|
||||||
|
rules: rules
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :name, :namespace, :rules
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,9 +3,10 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module Kubernetes
|
module Kubernetes
|
||||||
class RoleBinding
|
class RoleBinding
|
||||||
def initialize(name:, role_name:, namespace:, service_account_name:)
|
def initialize(name:, role_name:, role_kind:, namespace:, service_account_name:)
|
||||||
@name = name
|
@name = name
|
||||||
@role_name = role_name
|
@role_name = role_name
|
||||||
|
@role_kind = role_kind
|
||||||
@namespace = namespace
|
@namespace = namespace
|
||||||
@service_account_name = service_account_name
|
@service_account_name = service_account_name
|
||||||
end
|
end
|
||||||
|
@ -20,7 +21,7 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :name, :role_name, :namespace, :service_account_name
|
attr_reader :name, :role_name, :role_kind, :namespace, :service_account_name
|
||||||
|
|
||||||
def metadata
|
def metadata
|
||||||
{ name: name, namespace: namespace }
|
{ name: name, namespace: namespace }
|
||||||
|
@ -29,7 +30,7 @@ module Gitlab
|
||||||
def role_ref
|
def role_ref
|
||||||
{
|
{
|
||||||
apiGroup: 'rbac.authorization.k8s.io',
|
apiGroup: 'rbac.authorization.k8s.io',
|
||||||
kind: 'ClusterRole',
|
kind: role_kind,
|
||||||
name: role_name
|
name: role_name
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -176,6 +176,9 @@ describe Gitlab::Kubernetes::KubeClient do
|
||||||
let(:rbac_client) { client.rbac_client }
|
let(:rbac_client) { client.rbac_client }
|
||||||
|
|
||||||
[
|
[
|
||||||
|
:create_role,
|
||||||
|
:get_role,
|
||||||
|
:update_role,
|
||||||
:create_cluster_role_binding,
|
:create_cluster_role_binding,
|
||||||
:get_cluster_role_binding,
|
:get_cluster_role_binding,
|
||||||
:update_cluster_role_binding
|
:update_cluster_role_binding
|
||||||
|
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
||||||
|
|
||||||
describe Gitlab::Kubernetes::RoleBinding, '#generate' do
|
describe Gitlab::Kubernetes::RoleBinding, '#generate' do
|
||||||
let(:role_name) { 'edit' }
|
let(:role_name) { 'edit' }
|
||||||
|
let(:role_kind) { 'ClusterRole' }
|
||||||
let(:namespace) { 'my-namespace' }
|
let(:namespace) { 'my-namespace' }
|
||||||
let(:service_account_name) { 'my-service-account' }
|
let(:service_account_name) { 'my-service-account' }
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
|
||||||
let(:role_ref) do
|
let(:role_ref) do
|
||||||
{
|
{
|
||||||
apiGroup: 'rbac.authorization.k8s.io',
|
apiGroup: 'rbac.authorization.k8s.io',
|
||||||
kind: 'ClusterRole',
|
kind: role_kind,
|
||||||
name: role_name
|
name: role_name
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -37,6 +38,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
|
||||||
described_class.new(
|
described_class.new(
|
||||||
name: "gitlab-#{namespace}",
|
name: "gitlab-#{namespace}",
|
||||||
role_name: role_name,
|
role_name: role_name,
|
||||||
|
role_kind: role_kind,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
service_account_name: service_account_name
|
service_account_name: service_account_name
|
||||||
).generate
|
).generate
|
||||||
|
|
30
spec/lib/gitlab/kubernetes/role_spec.rb
Normal file
30
spec/lib/gitlab/kubernetes/role_spec.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::Kubernetes::Role do
|
||||||
|
let(:role) { described_class.new(name: name, namespace: namespace, rules: rules) }
|
||||||
|
let(:name) { 'example-name' }
|
||||||
|
let(:namespace) { 'example-namespace' }
|
||||||
|
|
||||||
|
let(:rules) do
|
||||||
|
[{
|
||||||
|
apiGroups: %w(hello.world),
|
||||||
|
resources: %w(oil diamonds coffee),
|
||||||
|
verbs: %w(say do walk run)
|
||||||
|
}]
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#generate' do
|
||||||
|
subject { role.generate }
|
||||||
|
|
||||||
|
let(:resource) do
|
||||||
|
::Kubeclient::Resource.new(
|
||||||
|
metadata: { name: name, namespace: namespace },
|
||||||
|
rules: rules
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(resource) }
|
||||||
|
end
|
||||||
|
end
|
|
@ -34,6 +34,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
|
||||||
stub_kubeclient_create_service_account(api_url, namespace: namespace)
|
stub_kubeclient_create_service_account(api_url, namespace: namespace)
|
||||||
stub_kubeclient_create_secret(api_url, namespace: namespace)
|
stub_kubeclient_create_secret(api_url, namespace: namespace)
|
||||||
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
|
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
|
||||||
|
stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
|
||||||
|
stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
|
||||||
|
|
||||||
stub_kubeclient_get_secret(
|
stub_kubeclient_get_secret(
|
||||||
api_url,
|
api_url,
|
||||||
|
|
|
@ -143,6 +143,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
|
||||||
|
|
||||||
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::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
|
||||||
|
stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'creates service account and token'
|
it_behaves_like 'creates service account and token'
|
||||||
|
@ -169,6 +171,24 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates a role and role binding granting knative serving permissions to the service account' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with(
|
||||||
|
body: hash_including(
|
||||||
|
metadata: {
|
||||||
|
name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
|
||||||
|
namespace: namespace
|
||||||
|
},
|
||||||
|
rules: [{
|
||||||
|
apiGroups: %w(serving.knative.dev),
|
||||||
|
resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services),
|
||||||
|
verbs: %w(get list create update delete patch watch)
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -199,6 +199,11 @@ module KubernetesHelpers
|
||||||
.to_return(kube_response({}))
|
.to_return(kube_response({}))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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}")
|
||||||
|
.to_return(kube_response({}))
|
||||||
|
end
|
||||||
|
|
||||||
def kube_v1_secret_body(**options)
|
def kube_v1_secret_body(**options)
|
||||||
{
|
{
|
||||||
"kind" => "SecretList",
|
"kind" => "SecretList",
|
||||||
|
|
Loading…
Reference in a new issue