diff options
Diffstat (limited to 'app/services/clusters/aws')
-rw-r--r-- | app/services/clusters/aws/authorize_role_service.rb | 49 | ||||
-rw-r--r-- | app/services/clusters/aws/fetch_credentials_service.rb | 28 | ||||
-rw-r--r-- | app/services/clusters/aws/proxy_service.rb | 134 |
3 files changed, 73 insertions, 138 deletions
diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb new file mode 100644 index 00000000000..6eafce0597e --- /dev/null +++ b/app/services/clusters/aws/authorize_role_service.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Clusters + module Aws + class AuthorizeRoleService + attr_reader :user + + Response = Struct.new(:status, :body) + + ERRORS = [ + ActiveRecord::RecordInvalid, + Clusters::Aws::FetchCredentialsService::MissingRoleError, + ::Aws::Errors::MissingCredentialsError, + ::Aws::STS::Errors::ServiceError + ].freeze + + def initialize(user, params:) + @user = user + @params = params + end + + def execute + @role = create_or_update_role! + + Response.new(:ok, credentials) + rescue *ERRORS + Response.new(:unprocessable_entity, {}) + end + + private + + attr_reader :role, :params + + def create_or_update_role! + if role = user.aws_role + role.update!(params) + + role + else + user.create_aws_role!(params) + end + end + + def credentials + Clusters::Aws::FetchCredentialsService.new(role).execute + end + end + end +end diff --git a/app/services/clusters/aws/fetch_credentials_service.rb b/app/services/clusters/aws/fetch_credentials_service.rb index 2724d4b657b..33efc4cc120 100644 --- a/app/services/clusters/aws/fetch_credentials_service.rb +++ b/app/services/clusters/aws/fetch_credentials_service.rb @@ -7,9 +7,8 @@ module Clusters MissingRoleError = Class.new(StandardError) - def initialize(provision_role, region:, provider: nil) + def initialize(provision_role, provider: nil) @provision_role = provision_role - @region = region @provider = provider end @@ -20,13 +19,14 @@ module Clusters client: client, role_arn: provision_role.role_arn, role_session_name: session_name, - external_id: provision_role.role_external_id + external_id: provision_role.role_external_id, + policy: session_policy ).credentials end private - attr_reader :provider, :region + attr_reader :provider def client ::Aws::STS::Client.new(credentials: gitlab_credentials, region: region) @@ -44,6 +44,26 @@ module Clusters Gitlab::CurrentSettings.eks_secret_access_key end + def region + provider&.region || Clusters::Providers::Aws::DEFAULT_REGION + end + + ## + # If we haven't created a provider record yet, + # we restrict ourselves to read only access so + # that we can safely expose credentials to the + # frontend (to be used when populating the + # creation form). + def session_policy + if provider.nil? + File.read(read_only_policy) + end + end + + def read_only_policy + Rails.root.join('vendor', 'aws', 'iam', "eks_cluster_read_only_policy.json") + end + def session_name if provider.present? "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}" diff --git a/app/services/clusters/aws/proxy_service.rb b/app/services/clusters/aws/proxy_service.rb deleted file mode 100644 index df8fc480005..00000000000 --- a/app/services/clusters/aws/proxy_service.rb +++ /dev/null @@ -1,134 +0,0 @@ -# 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 |