From 213ce7805856f2cc1d019a03c76ae0d098337c26 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 13 Nov 2019 06:06:38 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- app/controllers/clusters/clusters_controller.rb | 54 ++++++++- app/helpers/clusters_helper.rb | 2 +- app/models/clusters/cluster.rb | 6 +- app/models/clusters/providers/aws.rb | 13 +- app/models/clusters/providers/gcp.rb | 4 + app/models/project_services/data_fields.rb | 2 +- app/presenters/clusterable_presenter.rb | 12 ++ app/presenters/instance_clusterable_presenter.rb | 15 +++ .../clusters/aws/fetch_credentials_service.rb | 18 +-- app/services/clusters/aws/provision_service.rb | 12 +- app/services/clusters/aws/proxy_service.rb | 134 +++++++++++++++++++++ app/views/clusters/clusters/_banner.html.haml | 6 +- app/views/clusters/clusters/aws/_new.html.haml | 9 ++ 13 files changed, 266 insertions(+), 21 deletions(-) create mode 100644 app/services/clusters/aws/proxy_service.rb (limited to 'app') diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 5dd4040628f..5d6ce4f342c 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -3,12 +3,12 @@ class Clusters::ClustersController < Clusters::BaseController include RoutableActions - before_action :cluster, except: [:index, :new, :create_gcp, :create_user, :authorize_aws_role] + before_action :cluster, only: [:cluster_status, :show, :update, :destroy] before_action :generate_gcp_authorize_url, only: [:new] before_action :validate_gcp_token, only: [:new] before_action :gcp_cluster, only: [:new] before_action :user_cluster, only: [:new] - before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role] + before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role, :revoke_aws_role, :aws_proxy] before_action :authorize_update_cluster!, only: [:update] before_action :authorize_admin_cluster!, only: [:destroy] before_action :update_applications_status, only: [:cluster_status] @@ -117,6 +117,19 @@ class Clusters::ClustersController < Clusters::BaseController end end + def create_aws + @aws_cluster = ::Clusters::CreateService + .new(current_user, create_aws_cluster_params) + .execute + .present(current_user: current_user) + + if @aws_cluster.persisted? + head :created, location: @aws_cluster.show_path + else + render status: :unprocessable_entity, json: @aws_cluster.errors + end + end + def create_user @user_cluster = ::Clusters::CreateService .new(current_user, create_user_cluster_params) @@ -140,6 +153,21 @@ class Clusters::ClustersController < Clusters::BaseController role.save ? respond_201 : respond_422 end + def revoke_aws_role + current_user.aws_role&.destroy + + head :no_content + end + + def aws_proxy + response = Clusters::Aws::ProxyService.new( + current_user.aws_role, + params: params + ).execute + + render json: response.body, status: response.status + end + private def destroy_params @@ -200,6 +228,28 @@ class Clusters::ClustersController < Clusters::BaseController ) end + def create_aws_cluster_params + params.require(:cluster).permit( + :enabled, + :name, + :environment_scope, + :managed, + provider_aws_attributes: [ + :key_name, + :role_arn, + :region, + :vpc_id, + :instance_type, + :num_nodes, + :security_group_id, + subnet_ids: [] + ]).merge( + provider_type: :aws, + platform_type: :kubernetes, + clusterable: clusterable.subject + ) + end + def create_user_cluster_params params.require(:cluster).permit( :enabled, diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index 0cfb45a12e5..0037c49f134 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -40,7 +40,7 @@ module ClustersHelper def has_rbac_enabled?(cluster) return cluster.platform_kubernetes_rbac? if cluster.platform_kubernetes - !cluster.provider.legacy_abac? + cluster.provider.has_rbac_enabled? end end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 0db1fe9d6dc..ac2b63b194f 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -56,6 +56,7 @@ module Clusters has_many :kubernetes_namespaces accepts_nested_attributes_for :provider_gcp, update_only: true + accepts_nested_attributes_for :provider_aws, update_only: true accepts_nested_attributes_for :platform_kubernetes, update_only: true validates :name, cluster_name: true @@ -73,6 +74,7 @@ module Clusters delegate :status, to: :provider, allow_nil: true delegate :status_reason, to: :provider, allow_nil: true delegate :on_creation?, to: :provider, allow_nil: true + delegate :knative_pre_installed?, to: :provider, allow_nil: true delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true @@ -258,10 +260,6 @@ module Clusters end end - def knative_pre_installed? - provider&.knative_pre_installed? - end - private def unique_management_project_environment_scope diff --git a/app/models/clusters/providers/aws.rb b/app/models/clusters/providers/aws.rb index 89eb56aa41f..78eb75ddcc0 100644 --- a/app/models/clusters/providers/aws.rb +++ b/app/models/clusters/providers/aws.rb @@ -9,7 +9,6 @@ module Clusters self.table_name = 'cluster_providers_aws' belongs_to :cluster, inverse_of: :provider_aws, class_name: 'Clusters::Cluster' - belongs_to :created_by_user, class_name: 'User' default_value_for :region, 'us-east-1' default_value_for :num_nodes, 3 @@ -55,6 +54,18 @@ module Clusters ::Aws::Credentials.new(access_key_id, secret_access_key, session_token) end end + + def has_rbac_enabled? + true + end + + def knative_pre_installed? + false + end + + def created_by_user + cluster.user + end end end end diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb index f871674676f..2ca7d0249dc 100644 --- a/app/models/clusters/providers/gcp.rb +++ b/app/models/clusters/providers/gcp.rb @@ -54,6 +54,10 @@ module Clusters assign_attributes(operation_id: operation_id) end + def has_rbac_enabled? + !legacy_abac + end + def knative_pre_installed? cloud_run? end diff --git a/app/models/project_services/data_fields.rb b/app/models/project_services/data_fields.rb index cffb493d569..cf406a784ce 100644 --- a/app/models/project_services/data_fields.rb +++ b/app/models/project_services/data_fields.rb @@ -50,7 +50,7 @@ module DataFields end def data_fields_present? - data_fields.persisted? + data_fields.present? rescue NotImplementedError false end diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index d6f67c1f2e5..2306f55f1f4 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -29,10 +29,18 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated new_polymorphic_path([clusterable, :cluster], options) end + def aws_api_proxy_path(resource) + polymorphic_path([clusterable, :clusters], action: :aws_proxy, resource: resource) + end + def authorize_aws_role_path polymorphic_path([clusterable, :clusters], action: :authorize_aws_role) end + def revoke_aws_role_path + polymorphic_path([clusterable, :clusters], action: :revoke_aws_role) + end + def create_user_clusters_path polymorphic_path([clusterable, :clusters], action: :create_user) end @@ -41,6 +49,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated polymorphic_path([clusterable, :clusters], action: :create_gcp) end + def create_aws_clusters_path + polymorphic_path([clusterable, :clusters], action: :create_aws) + end + def cluster_status_cluster_path(cluster, params = {}) raise NotImplementedError end diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb index f820c0f6b42..c6572e8ce71 100644 --- a/app/presenters/instance_clusterable_presenter.rb +++ b/app/presenters/instance_clusterable_presenter.rb @@ -52,11 +52,26 @@ class InstanceClusterablePresenter < ClusterablePresenter create_gcp_admin_clusters_path end + override :create_aws_clusters_path + def create_aws_clusters_path + create_aws_admin_clusters_path + end + override :authorize_aws_role_path def authorize_aws_role_path authorize_aws_role_admin_clusters_path end + override :revoke_aws_role_path + def revoke_aws_role_path + revoke_aws_role_admin_clusters_path + end + + override :aws_api_proxy_path + def aws_api_proxy_path(resource) + aws_proxy_admin_clusters_path(resource: resource) + end + override :empty_state_help_text def empty_state_help_text s_('ClusterIntegration|Adding an integration will share the cluster across all projects.') diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb index 29442208c62..2724d4b657b 100644 --- a/app/services/clusters/aws/fetch_credentials_service.rb +++ b/app/services/clusters/aws/fetch_credentials_service.rb @@ -3,11 +3,13 @@ module Clusters module Aws class FetchCredentialsService - attr_reader :provider + attr_reader :provision_role MissingRoleError = Class.new(StandardError) - def initialize(provider) + def initialize(provision_role, region:, provider: nil) + @provision_role = provision_role + @region = region @provider = provider end @@ -24,12 +26,10 @@ module Clusters private - def provision_role - provider.created_by_user.aws_role - end + attr_reader :provider, :region def client - ::Aws::STS::Client.new(credentials: gitlab_credentials, region: provider.region) + ::Aws::STS::Client.new(credentials: gitlab_credentials, region: region) end def gitlab_credentials @@ -45,7 +45,11 @@ module Clusters end def session_name - "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provider.created_by_user_id}" + if provider.present? + "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}" + else + "gitlab-eks-autofill-user-#{provision_role.user_id}" + end end end end diff --git a/app/services/clusters/aws/provision_service.rb b/app/services/clusters/aws/provision_service.rb index 6a3b0dffae2..35fe8433b4d 100644 --- a/app/services/clusters/aws/provision_service.rb +++ b/app/services/clusters/aws/provision_service.rb @@ -21,7 +21,7 @@ module Clusters end rescue Clusters::Aws::FetchCredentialsService::MissingRoleError provider.make_errored!('Amazon role is not configured') - rescue ::Aws::Errors::MissingCredentialsError, Settingslogic::MissingSetting + rescue ::Aws::Errors::MissingCredentialsError provider.make_errored!('Amazon credentials are not configured') rescue ::Aws::STS::Errors::ServiceError => e provider.make_errored!("Amazon authentication failed; #{e.message}") @@ -31,8 +31,16 @@ module Clusters private + def provision_role + provider.created_by_user&.aws_role + end + def credentials - @credentials ||= Clusters::Aws::FetchCredentialsService.new(provider).execute + @credentials ||= Clusters::Aws::FetchCredentialsService.new( + provision_role, + provider: provider, + region: provider.region + ).execute end def configure_provider_credentials diff --git a/app/services/clusters/aws/proxy_service.rb b/app/services/clusters/aws/proxy_service.rb new file mode 100644 index 00000000000..df8fc480005 --- /dev/null +++ b/app/services/clusters/aws/proxy_service.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +module Clusters + module Aws + class ProxyService + DEFAULT_REGION = 'us-east-1' + + BadRequest = Class.new(StandardError) + Response = Struct.new(:status, :body) + + def initialize(role, params:) + @role = role + @params = params + end + + def execute + api_response = request_from_api! + + Response.new(:ok, api_response.to_hash) + rescue *service_errors + Response.new(:bad_request, {}) + end + + private + + attr_reader :role, :params + + def request_from_api! + case requested_resource + when 'key_pairs' + ec2_client.describe_key_pairs + + when 'instance_types' + instance_types + + when 'roles' + iam_client.list_roles + + when 'regions' + ec2_client.describe_regions + + when 'security_groups' + raise BadRequest unless vpc_id.present? + + ec2_client.describe_security_groups(vpc_filter) + + when 'subnets' + raise BadRequest unless vpc_id.present? + + ec2_client.describe_subnets(vpc_filter) + + when 'vpcs' + ec2_client.describe_vpcs + + else + raise BadRequest + end + end + + def requested_resource + params[:resource] + end + + def vpc_id + params[:vpc_id] + end + + def region + params[:region] || DEFAULT_REGION + end + + def vpc_filter + { + filters: [{ + name: "vpc-id", + values: [vpc_id] + }] + } + end + + ## + # Unfortunately the EC2 API doesn't provide a list of + # possible instance types. There is a workaround, using + # the Pricing API, but instead of requiring the + # user to grant extra permissions for this we use the + # values that validate the CloudFormation template. + def instance_types + { + instance_types: cluster_stack_instance_types.map { |type| Hash(instance_type_name: type) } + } + end + + def cluster_stack_instance_types + YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues') + end + + def stack_template + File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml')) + end + + def ec2_client + ::Aws::EC2::Client.new(client_options) + end + + def iam_client + ::Aws::IAM::Client.new(client_options) + end + + def credentials + Clusters::Aws::FetchCredentialsService.new(role, region: region).execute + end + + def client_options + { + credentials: credentials, + region: region, + http_open_timeout: 5, + http_read_timeout: 10 + } + end + + def service_errors + [ + BadRequest, + Clusters::Aws::FetchCredentialsService::MissingRoleError, + ::Aws::Errors::MissingCredentialsError, + ::Aws::EC2::Errors::ServiceError, + ::Aws::IAM::Errors::ServiceError, + ::Aws::STS::Errors::ServiceError + ] + end + end + end +end diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml index 4b4278075a6..7d97aaccbcf 100644 --- a/app/views/clusters/clusters/_banner.html.haml +++ b/app/views/clusters/clusters/_banner.html.haml @@ -1,10 +1,10 @@ .hidden.js-cluster-error.bs-callout.bs-callout-danger{ role: 'alert' } - = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine') + = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster') %p.js-error-reason .hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' } %span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' } - %span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...') + %span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created...') .hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' } .col-11 @@ -19,4 +19,4 @@ %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×" .hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' } - = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine.") + = s_("ClusterIntegration|Kubernetes cluster was successfully created.") diff --git a/app/views/clusters/clusters/aws/_new.html.haml b/app/views/clusters/clusters/aws/_new.html.haml index fe8b606af70..48467f88f52 100644 --- a/app/views/clusters/clusters/aws/_new.html.haml +++ b/app/views/clusters/clusters/aws/_new.html.haml @@ -4,6 +4,15 @@ - else .js-create-eks-cluster-form-container{ data: { 'gitlab-managed-cluster-help-path' => help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), 'create-role-path' => clusterable.authorize_aws_role_path, + 'sign-out-path' => clusterable.revoke_aws_role_path, + 'create-cluster-path' => clusterable.create_aws_clusters_path, + 'get-roles-path' => clusterable.aws_api_proxy_path('roles'), + 'get-regions-path' => clusterable.aws_api_proxy_path('regions'), + 'get-key-pairs-path' => clusterable.aws_api_proxy_path('key_pairs'), + 'get-vpcs-path' => clusterable.aws_api_proxy_path('vpcs'), + 'get-subnets-path' => clusterable.aws_api_proxy_path('subnets'), + 'get-security-groups-path' => clusterable.aws_api_proxy_path('security_groups'), + 'get-instance-types-path' => clusterable.aws_api_proxy_path('instance_types'), 'account-id' => Gitlab::CurrentSettings.eks_account_id, 'external-id' => @aws_role.role_external_id, 'kubernetes-integration-help-path' => help_page_path('user/project/clusters/index'), -- cgit v1.2.3