From 3a9fef6021912250cba76cd27858edba8022cc75 Mon Sep 17 00:00:00 2001 From: Adrien Kohlbecker <adrien.kohlbecker@gmail.com> Date: Thu, 4 Jun 2020 20:42:47 +0000 Subject: [PATCH] Support IAP protected prometheus installations Add support for IAP --- Gemfile | 4 +- Gemfile.lock | 29 ++++++------ app/controllers/concerns/service_params.rb | 2 + .../project_services/prometheus_service.rb | 42 +++++++++++++++++- .../unreleased/ak-update-google-auth.yml | 5 +++ config/initializers/google_api_client.rb | 19 ++------ doc/api/services.md | 2 + lib/api/helpers/services_helpers.rb | 12 +++++ locale/gitlab.pot | 6 +++ spec/initializers/google_api_client_spec.rb | 17 ------- .../prometheus_service_spec.rb | 44 +++++++++++++++++++ 11 files changed, 132 insertions(+), 50 deletions(-) create mode 100644 changelogs/unreleased/ak-update-google-auth.yml delete mode 100644 spec/initializers/google_api_client_spec.rb diff --git a/Gemfile b/Gemfile index 7d63a5f50c040..8137b66c2836a 100644 --- a/Gemfile +++ b/Gemfile @@ -112,14 +112,14 @@ gem 'fog-aws', '~> 3.5' # Locked until fog-google resolves https://github.com/fog/fog-google/issues/421. # Also see config/initializers/fog_core_patch.rb. gem 'fog-core', '= 2.1.0' -gem 'fog-google', '~> 1.9' +gem 'fog-google', '~> 1.10' gem 'fog-local', '~> 0.6' gem 'fog-openstack', '~> 1.0' gem 'fog-rackspace', '~> 0.1.1' gem 'fog-aliyun', '~> 0.3' # for Google storage -gem 'google-api-client', '~> 0.23' +gem 'google-api-client', '~> 0.33' # for aws storage gem 'unf', '~> 0.1.4' diff --git a/Gemfile.lock b/Gemfile.lock index 31ff0fb304c61..49a0113f510d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -286,7 +286,7 @@ GEM factory_bot_rails (5.1.0) factory_bot (~> 5.1.0) railties (>= 4.2.0) - faraday (0.15.4) + faraday (0.17.3) multipart-post (>= 1.2, < 3) faraday-http-cache (2.0.0) faraday (~> 0.8) @@ -330,11 +330,11 @@ GEM excon (~> 0.58) formatador (~> 0.2) mime-types - fog-google (1.9.1) + fog-google (1.10.0) fog-core (<= 2.1.0) fog-json (~> 1.2) fog-xml (~> 0.1.0) - google-api-client (~> 0.23.0) + google-api-client (>= 0.32, < 0.34) fog-json (1.2.0) fog-core multi_json (~> 1.10) @@ -419,23 +419,24 @@ GEM actionpack (>= 3.0) multi_json request_store (>= 1.0) - google-api-client (0.23.4) + google-api-client (0.33.2) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.7.0) + googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) + mini_mime (~> 1.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) + signet (~> 0.12) google-protobuf (3.8.0) googleapis-common-protos-types (1.0.4) google-protobuf (~> 3.0) - googleauth (0.6.6) - faraday (~> 0.12) + googleauth (0.12.0) + faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) - memoist (~> 0.12) + memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.7) + signet (~> 0.14) gpgme (2.0.20) mini_portile2 (~> 2.3) grape (1.1.0) @@ -1010,9 +1011,9 @@ GEM sidekiq-cron (1.0.4) fugit (~> 1.1) sidekiq (>= 4.2.1) - signet (0.11.0) + signet (0.14.0) addressable (~> 2.3) - faraday (~> 0.9) + faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simple_po_parser (1.1.2) @@ -1224,7 +1225,7 @@ DEPENDENCIES fog-aliyun (~> 0.3) fog-aws (~> 3.5) fog-core (= 2.1.0) - fog-google (~> 1.9) + fog-google (~> 1.10) fog-local (~> 0.6) fog-openstack (~> 1.0) fog-rackspace (~> 0.1.1) @@ -1250,7 +1251,7 @@ DEPENDENCIES gitlab_chronic_duration (~> 0.10.6.2) gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.2) - google-api-client (~> 0.23) + google-api-client (~> 0.33) google-protobuf (~> 3.8.0) gpgme (~> 2.0.19) grape (~> 1.1.0) diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index e2c83f9a06913..66e01ac525067 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -28,6 +28,8 @@ module ServiceParams :drone_url, :enable_ssl_verification, :external_wiki_url, + :google_iap_service_account_json, + :google_iap_audience_client_id, # We're using `issues_events` and `merge_requests_events` # in the view so we still need to explicitly state them # here. `Service#event_names` would only give diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 4a28d1ff2b004..b3a4c8db02d80 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -5,6 +5,8 @@ class PrometheusService < MonitoringService # Access to prometheus is directly through the API prop_accessor :api_url + prop_accessor :google_iap_service_account_json + prop_accessor :google_iap_audience_client_id boolean_accessor :manual_configuration # We need to allow the self-monitoring project to connect to the internal @@ -49,7 +51,7 @@ def self.to_param end def fields - [ + result = [ { type: 'checkbox', name: 'manual_configuration', @@ -64,6 +66,27 @@ def fields required: true } ] + + if Feature.enabled?(:prometheus_service_iap_auth) + result += [ + { + type: 'text', + name: 'google_iap_audience_client_id', + title: 'Google IAP Audience Client ID', + placeholder: s_('PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)'), + required: false + }, + { + type: 'textarea', + name: 'google_iap_service_account_json', + title: 'Google IAP Service Account JSON', + placeholder: s_('PrometheusService|Contents of the credentials.json file of your service account, like: { "type": "service_account", "project_id": ... }'), + required: false + } + ] + end + + result end # Check we can connect to the Prometheus API @@ -77,7 +100,14 @@ def test(*args) def prometheus_client return unless should_return_client? - Gitlab::PrometheusClient.new(api_url, allow_local_requests: allow_local_api_url?) + options = { allow_local_requests: allow_local_api_url? } + + if Feature.enabled?(:prometheus_service_iap_auth) && behind_iap? + # Adds the Authorization header + options[:headers] = iap_client.apply({}) + end + + Gitlab::PrometheusClient.new(api_url, options) end def prometheus_available? @@ -149,4 +179,12 @@ def create_default_alerts Prometheus::CreateDefaultAlertsWorker.perform_async(project_id) end + + def behind_iap? + manual_configuration? && google_iap_audience_client_id.present? && google_iap_service_account_json.present? + end + + def iap_client + @iap_client ||= Google::Auth::Credentials.new(Gitlab::Json.parse(google_iap_service_account_json), target_audience: google_iap_audience_client_id).client + end end diff --git a/changelogs/unreleased/ak-update-google-auth.yml b/changelogs/unreleased/ak-update-google-auth.yml new file mode 100644 index 0000000000000..cb91b9ebe93e3 --- /dev/null +++ b/changelogs/unreleased/ak-update-google-auth.yml @@ -0,0 +1,5 @@ +--- +title: Support IAP protected prometheus installations +merge_request: 33508 +author: +type: added diff --git a/config/initializers/google_api_client.rb b/config/initializers/google_api_client.rb index 443bb29fb5253..49a35e5bd7ccd 100644 --- a/config/initializers/google_api_client.rb +++ b/config/initializers/google_api_client.rb @@ -1,23 +1,12 @@ # frozen_string_literal: true -# -# google-api-client >= 0.26.0 supports enabling CloudRun and Istio during -# cluster creation, but fog-google currently hard deps on '~> 0.23.0', which -# prevents us from upgrading. We are injecting these options as hashes below -# as a workaround until this is resolved. -# -# This can be removed once fog-google and google-api-client can be upgraded. -# See https://gitlab.com/gitlab-org/gitlab/issues/31280 for more details. -# -require 'google/apis/container_v1beta1' require 'google/apis/options' +# these require solve load order issues (undefined constant Google::Apis::ServerError and Signet::RemoteServerError, rescued in multiple places) +require 'google/apis/errors' +require 'signet/errors' + # As stated in https://github.com/googleapis/google-api-ruby-client#errors--retries, # enabling retries is strongly encouraged but disabled by default. Large uploads # that may hit timeouts will mainly benefit from this. Google::Apis::RequestOptions.default.retries = 3 if Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_GOOGLE_API_RETRIES', true)) - -Google::Apis::ContainerV1beta1::AddonsConfig::Representation.tap do |representation| - representation.hash :cloud_run_config, as: 'cloudRunConfig' - representation.hash :istio_config, as: 'istioConfig' -end diff --git a/doc/api/services.md b/doc/api/services.md index b1549dacd2b3d..02048a27c1b27 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -1007,6 +1007,8 @@ Parameters: | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `api_url` | string | true | Prometheus API Base URL. For example, `http://prometheus.example.com/`. | +| `google_iap_audience_client_id` | string | false | Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com) | +| `google_iap_service_account_json` | string | false | credentials.json file for your service account, like { "type": "service_account", "project_id": ... } | ### Delete Prometheus service diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 02e60ff5db5ba..3d6039cacaa04 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -583,6 +583,18 @@ def self.services name: :api_url, type: String, desc: 'Prometheus API Base URL, like http://prometheus.example.com/' + }, + { + required: true, + name: :google_iap_audience_client_id, + type: String, + desc: 'Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)' + }, + { + required: true, + name: :google_iap_service_account_json, + type: String, + desc: 'Contents of the credentials.json file of your service account, like: { "type": "service_account", "project_id": ... }' } ], 'pushover' => [ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 047bba075cdfe..4baf53d88d937 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17475,12 +17475,18 @@ msgstr "" msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" msgstr "" +msgid "PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)" +msgstr "" + msgid "PrometheusService|Common metrics" msgstr "" msgid "PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters." msgstr "" +msgid "PrometheusService|Contents of the credentials.json file of your service account, like: { \"type\": \"service_account\", \"project_id\": ... }" +msgstr "" + msgid "PrometheusService|Custom metrics" msgstr "" diff --git a/spec/initializers/google_api_client_spec.rb b/spec/initializers/google_api_client_spec.rb deleted file mode 100644 index 44a1bc0836cdc..0000000000000 --- a/spec/initializers/google_api_client_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe './config/initializers/google_api_client.rb' do - subject { Google::Apis::ContainerV1beta1 } - - it 'is needed' do |example| - is_expected.not_to be_const_defined(:CloudRunConfig), - <<-MSG.strip_heredoc - The google-api-client gem has been upgraded! - Remove: - #{example.example_group.description} - #{example.file_path} - MSG - end -end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index a85dbe3a7df0f..ec5bef92cde0f 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -252,6 +252,26 @@ end end end + + context 'behind IAP' do + let(:manual_configuration) { true } + + before do + # dummy private key generated only for this test to pass openssl validation + service.google_iap_service_account_json = '{"type":"service_account","private_key":"-----BEGIN RSA PRIVATE KEY-----\nMIIBOAIBAAJAU85LgUY5o6j6j/07GMLCNUcWJOBA1buZnNgKELayA6mSsHrIv31J\nY8kS+9WzGPQninea7DcM4hHA7smMgQD1BwIDAQABAkAqKxMy6PL3tn7dFL43p0ex\nJyOtSmlVIiAZG1t1LXhE/uoLpYi5DnbYqGgu0oih+7nzLY/dXpNpXUmiRMOUEKmB\nAiEAoTi2rBXbrLSi2C+H7M/nTOjMQQDuZ8Wr4uWpKcjYJTMCIQCFEskL565oFl/7\nRRQVH+cARrAsAAoJSbrOBAvYZ0PI3QIgIEFwis10vgEF86rOzxppdIG/G+JL0IdD\n9IluZuXAGPECIGUo7qSaLr75o2VEEgwtAFH5aptIPFjrL5LFCKwtdB4RAiAYZgFV\nHCMmaooAw/eELuMoMWNYmujZ7VaAnOewGDW0uw==\n-----END RSA PRIVATE KEY-----\n"}' + service.google_iap_audience_client_id = "IAP_CLIENT_ID.apps.googleusercontent.com" + + stub_request(:post, "https://oauth2.googleapis.com/token").to_return(status: 200, body: '{"id_token": "FOO"}', headers: { 'Content-Type': 'application/json; charset=UTF-8' }) + + stub_feature_flags(prometheus_service_iap_auth: true) + end + + it 'includes the authorization header' do + expect(service.prometheus_client).not_to be_nil + expect(service.prometheus_client.send(:options)).to have_key(:headers) + expect(service.prometheus_client.send(:options)[:headers]).to eq(authorization: "Bearer FOO") + end + end end describe '#prometheus_available?' do @@ -457,9 +477,33 @@ } ] end + let(:feature_flagged_fields) do + [ + { + type: 'text', + name: 'google_iap_audience_client_id', + title: 'Google IAP Audience Client ID', + placeholder: s_('PrometheusService|Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)'), + required: false + }, + { + type: 'textarea', + name: 'google_iap_service_account_json', + title: 'Google IAP Service Account JSON', + placeholder: s_('PrometheusService|Contents of the credentials.json file of your service account, like: { "type": "service_account", "project_id": ... }'), + required: false + } + ] + end it 'returns fields' do + stub_feature_flags(prometheus_service_iap_auth: false) expect(service.fields).to eq(expected_fields) end + + it 'returns fields with feature flag on' do + stub_feature_flags(prometheus_service_iap_auth: true) + expect(service.fields).to eq(expected_fields + feature_flagged_fields) + end end end -- GitLab