Commit 462740c0 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch 'incubation-5mp-service-account-policy' into 'master'

Grant permissions to generated Gcp service account

See merge request gitlab-org/gitlab!78712
parents b069a9cd c03d5b8f
......@@ -5,6 +5,7 @@ module GoogleCloud
def execute
service_account = google_api_client.create_service_account(gcp_project_id, service_account_name, service_account_desc)
service_account_key = google_api_client.create_service_account_key(gcp_project_id, service_account.unique_id)
google_api_client.grant_service_account_roles(gcp_project_id, service_account.email)
service_accounts_service.add_for_project(
environment_name,
......@@ -35,7 +36,7 @@ module GoogleCloud
end
def google_api_client
GoogleApi::CloudPlatform::Client.new(google_oauth2_token, nil)
@google_api_client_instance ||= GoogleApi::CloudPlatform::Client.new(google_oauth2_token, nil)
end
def service_accounts_service
......@@ -50,7 +51,7 @@ module GoogleCloud
"GitLab generated service account for project '#{project.name}' and environment '#{environment_name}'"
end
# Overriden in EE
# Overridden in EE
def environment_protected?
false
end
......
......@@ -2,10 +2,6 @@
require 'spec_helper'
# Mock Types
MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret)
MockServiceAccount = Struct.new(:project_id, :unique_id)
RSpec.describe GoogleCloud::CreateServiceAccountsService do
let(:google_oauth2_token) { 'mock-token' }
let(:gcp_project_id) { 'mock-gcp-project-id' }
......@@ -14,19 +10,26 @@ RSpec.describe GoogleCloud::CreateServiceAccountsService do
before do
stub_licensed_features(protected_environments: true)
mock_google_oauth2_creds = Struct.new(:app_id, :app_secret)
.new('mock-app-id', 'mock-app-secret')
allow(Gitlab::Auth::OAuth::Provider)
.to receive(:config_for)
.with('google_oauth2')
.and_return(MockGoogleOAuth2Credentials.new('mock-app-id', 'mock-app-secret'))
.and_return(mock_google_oauth2_creds)
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
allow(client)
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
mock_service_account = Struct.new(:project_id, :unique_id, :email)
.new('mock-project-id', 'mock-unique-id', 'mock-email')
expect(client)
.to receive(:create_service_account)
.and_return(MockServiceAccount.new('mock-project-id', 'mock-unique-id'))
.and_return(mock_service_account)
allow(client)
expect(client)
.to receive(:create_service_account_key)
.and_return('mock-key')
expect(client)
.to receive(:grant_service_account_roles)
end
end
......
......@@ -20,6 +20,7 @@ module GoogleApi
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring"
].freeze
ROLES_LIST = %w[roles/iam.serviceAccountUser roles/artifactregistry.admin roles/cloudbuild.builds.builder roles/run.admin roles/storage.admin roles/cloudsql.admin roles/browser].freeze
class << self
def session_key_for_token
......@@ -88,11 +89,8 @@ module GoogleApi
def list_projects
result = []
service = Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new
service.authorization = access_token
response = service.fetch_all(items: :projects) do |token|
service.list_projects
response = cloud_resource_manager_service.fetch_all(items: :projects) do |token|
cloud_resource_manager_service.list_projects
end
# Google API results are paged by default, so we need to iterate through
......@@ -130,6 +128,11 @@ module GoogleApi
service.create_service_account_key(name, request_body)
end
def grant_service_account_roles(gcp_project_id, email)
body = policy_request_body(gcp_project_id, email)
cloud_resource_manager_service.set_project_iam_policy(gcp_project_id, body)
end
private
def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons)
......@@ -173,6 +176,23 @@ module GoogleApi
options.header = { 'User-Agent': "GitLab/#{Gitlab::VERSION.match('(\d+\.\d+)').captures.first} (GPN:GitLab;)" }
end
end
def policy_request_body(gcp_project_id, email)
policy = cloud_resource_manager_service.get_project_iam_policy(gcp_project_id)
policy.bindings = policy.bindings + additional_policy_bindings("serviceAccount:#{email}")
Google::Apis::CloudresourcemanagerV1::SetIamPolicyRequest.new(policy: policy)
end
def additional_policy_bindings(member)
ROLES_LIST.map do |role|
Google::Apis::CloudresourcemanagerV1::Binding.new(role: role, members: [member])
end
end
def cloud_resource_manager_service
@gpc_service ||= Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new.tap { |s| s. authorization = access_token }
end
end
end
end
......@@ -60,7 +60,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
.to receive(:get_zone_cluster).with(any_args, options: user_agent_options)
.and_return(gke_cluster)
.and_return(gke_cluster)
end
it { is_expected.to eq(gke_cluster) }
......@@ -122,7 +122,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
before do
allow_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService)
.to receive(:create_cluster).with(any_args)
.and_return(operation)
.and_return(operation)
end
it 'sets corresponded parameters' do
......@@ -172,7 +172,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
.to receive(:get_zone_operation).with(any_args, options: user_agent_options)
.and_return(operation)
.and_return(operation)
end
it { is_expected.to eq(operation) }
......@@ -244,7 +244,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
let(:operation) { double('Service Account Key') }
it 'class Google Api IamService#create_service_account_key' do
it 'calls Google Api IamService#create_service_account_key' do
expect_any_instance_of(Google::Apis::IamV1::IamService)
.to receive(:create_service_account_key)
.with(any_args)
......@@ -252,4 +252,48 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
is_expected.to eq(operation)
end
end
describe 'grant_service_account_roles' do
subject { client.grant_service_account_roles(spy, spy) }
it 'calls Google Api CloudResourceManager#set_iam_policy' do
mock_gcp_id = 'mock-gcp-id'
mock_email = 'mock@email.com'
mock_policy = Struct.new(:bindings).new([])
mock_body = []
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/iam.serviceAccountUser', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/artifactregistry.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/cloudbuild.builds.builder', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/run.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/storage.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/cloudsql.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/browser', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::SetIamPolicyRequest).to receive(:new).and_return([])
expect_next_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService) do |instance|
expect(instance).to receive(:get_project_iam_policy)
.with(mock_gcp_id)
.and_return(mock_policy)
expect(instance).to receive(:set_project_iam_policy)
.with(mock_gcp_id, mock_body)
end
client.grant_service_account_roles(mock_gcp_id, mock_email)
end
end
end
......@@ -2,10 +2,6 @@
require 'spec_helper'
# Mock Types
MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret)
MockServiceAccount = Struct.new(:project_id, :unique_id)
RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
let_it_be(:project) { create(:project, :public) }
......@@ -86,10 +82,12 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
context 'and user has successfully completed the google oauth2 flow' do
before do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
mock_service_account = Struct.new(:project_id, :unique_id, :email).new(123, 456, 'em@ai.l')
allow(client).to receive(:validate_token).and_return(true)
allow(client).to receive(:list_projects).and_return([{}, {}, {}])
allow(client).to receive(:create_service_account).and_return(MockServiceAccount.new(123, 456))
allow(client).to receive(:create_service_account).and_return(mock_service_account)
allow(client).to receive(:create_service_account_key).and_return({})
allow(client).to receive(:grant_service_account_roles)
end
end
......@@ -147,7 +145,8 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
context 'but gitlab instance is not configured for google oauth2' do
before do
unconfigured_google_oauth2 = MockGoogleOAuth2Credentials.new('', '')
unconfigured_google_oauth2 = Struct.new(:app_id, :app_secret)
.new('', '')
allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for)
.with('google_oauth2')
.and_return(unconfigured_google_oauth2)
......
......@@ -2,22 +2,26 @@
require 'spec_helper'
# Mock Types
MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret)
MockServiceAccount = Struct.new(:project_id, :unique_id)
RSpec.describe GoogleCloud::CreateServiceAccountsService do
describe '#execute' do
before do
mock_google_oauth2_creds = Struct.new(:app_id, :app_secret)
.new('mock-app-id', 'mock-app-secret')
allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for)
.with('google_oauth2')
.and_return(MockGoogleOAuth2Credentials.new('mock-app-id', 'mock-app-secret'))
.and_return(mock_google_oauth2_creds)
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
mock_service_account = Struct.new(:project_id, :unique_id, :email)
.new('mock-project-id', 'mock-unique-id', 'mock-email')
allow(client).to receive(:create_service_account)
.and_return(MockServiceAccount.new('mock-project-id', 'mock-unique-id'))
.and_return(mock_service_account)
allow(client).to receive(:create_service_account_key)
.and_return('mock-key')
allow(client)
.to receive(:grant_service_account_roles)
end
end
......
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