summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-12 00:07:43 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-12 00:07:43 +0000
commit2e3cbf7d89815e2915f77677388c49b48f8d20c3 (patch)
tree03bdbc99e829295e8077b2ec4032300c15b48e37 /app
parente44bb86539a8fb4cfb06dfe281632b6f206bd0a7 (diff)
downloadgitlab-ce-2e3cbf7d89815e2915f77677388c49b48f8d20c3.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue13
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue2
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/index.js22
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js148
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/actions.js13
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/index.js4
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/index.js30
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js3
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/store/state.js2
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/clusters/clusters_controller.rb36
-rw-r--r--app/controllers/projects/error_tracking_controller.rb4
-rw-r--r--app/models/clusters/providers/aws.rb4
-rw-r--r--app/models/deploy_key.rb10
-rw-r--r--app/models/user.rb2
-rw-r--r--app/policies/deploy_key_policy.rb5
-rw-r--r--app/presenters/clusterable_presenter.rb8
-rw-r--r--app/presenters/instance_clusterable_presenter.rb10
-rw-r--r--app/presenters/projects/settings/deploy_keys_presenter.rb62
-rw-r--r--app/serializers/deploy_key_entity.rb11
-rw-r--r--app/services/clusters/aws/authorize_role_service.rb49
-rw-r--r--app/services/clusters/aws/fetch_credentials_service.rb28
-rw-r--r--app/services/clusters/aws/proxy_service.rb134
-rw-r--r--app/services/clusters/kubernetes.rb (renamed from app/services/clusters/kubernetes/kubernetes.rb)0
-rw-r--r--app/views/clusters/clusters/aws/_new.html.haml13
26 files changed, 291 insertions, 327 deletions
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
index 641343b8150..d04d0ff2a6d 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
@@ -22,10 +22,7 @@ const {
mapState: mapSecurityGroupsState,
mapActions: mapSecurityGroupsActions,
} = createNamespacedHelpers('securityGroups');
-const {
- mapState: mapInstanceTypesState,
- mapActions: mapInstanceTypesActions,
-} = createNamespacedHelpers('instanceTypes');
+const { mapState: mapInstanceTypesState } = createNamespacedHelpers('instanceTypes');
export default {
components: {
@@ -265,12 +262,10 @@ export default {
mounted() {
this.fetchRegions();
this.fetchRoles();
- this.fetchInstanceTypes();
},
methods: {
...mapActions([
'createCluster',
- 'signOut',
'setClusterName',
'setEnvironmentScope',
'setKubernetesVersion',
@@ -290,7 +285,6 @@ export default {
...mapRolesActions({ fetchRoles: 'fetchItems' }),
...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }),
...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }),
- ...mapInstanceTypesActions({ fetchInstanceTypes: 'fetchItems' }),
setRegionAndFetchVpcsAndKeyPairs(region) {
this.setRegion({ region });
this.setVpc({ vpc: null });
@@ -316,11 +310,6 @@ export default {
{{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }}
</h2>
<div class="mb-3" v-html="kubernetesIntegrationHelpText"></div>
- <div class="mb-3">
- <button class="btn btn-link js-sign-out" @click.prevent="signOut()">
- {{ s__('ClusterIntegration|Select a different AWS role') }}
- </button>
- </div>
<div class="form-group">
<label class="label-bold" for="eks-cluster-name">{{
s__('ClusterIntegration|Kubernetes cluster name')
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
index 50a23536451..1dd4c468ae6 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
@@ -28,7 +28,7 @@ export default {
},
data() {
return {
- roleArn: '',
+ roleArn: this.$store.state.roleArn,
};
},
computed: {
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/index.js b/app/assets/javascripts/create_cluster/eks_cluster/index.js
index 27f859d8972..fb993a7aa59 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/index.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/index.js
@@ -12,20 +12,14 @@ export default el => {
kubernetesIntegrationHelpPath,
accountAndExternalIdsHelpPath,
createRoleArnHelpPath,
- getRolesPath,
- getRegionsPath,
- getKeyPairsPath,
- getVpcsPath,
- getSubnetsPath,
- getSecurityGroupsPath,
- getInstanceTypesPath,
externalId,
accountId,
+ instanceTypes,
hasCredentials,
createRolePath,
createClusterPath,
- signOutPath,
externalLinkIcon,
+ roleArn,
} = el.dataset;
return new Vue({
@@ -35,18 +29,10 @@ export default el => {
hasCredentials: parseBoolean(hasCredentials),
externalId,
accountId,
+ instanceTypes: JSON.parse(instanceTypes),
createRolePath,
createClusterPath,
- signOutPath,
- },
- apiPaths: {
- getRolesPath,
- getRegionsPath,
- getKeyPairsPath,
- getVpcsPath,
- getSubnetsPath,
- getSecurityGroupsPath,
- getInstanceTypesPath,
+ roleArn,
},
}),
components: {
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
index 21b87d525cf..601ff6f9adc 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js
@@ -1,58 +1,98 @@
-import axios from '~/lib/utils/axios_utils';
-
-export default apiPaths => ({
- fetchRoles() {
- return axios
- .get(apiPaths.getRolesPath)
- .then(({ data: { roles } }) =>
- roles.map(({ role_name: name, arn: value }) => ({ name, value })),
- );
- },
- fetchKeyPairs({ region }) {
- return axios
- .get(apiPaths.getKeyPairsPath, { params: { region } })
- .then(({ data: { key_pairs: keyPairs } }) =>
- keyPairs.map(({ key_name }) => ({ name: key_name, value: key_name })),
- );
- },
- fetchRegions() {
- return axios.get(apiPaths.getRegionsPath).then(({ data: { regions } }) =>
- regions.map(({ region_name }) => ({
- name: region_name,
- value: region_name,
+import AWS from 'aws-sdk/global';
+import EC2 from 'aws-sdk/clients/ec2';
+import IAM from 'aws-sdk/clients/iam';
+
+const lookupVpcName = ({ Tags: tags, VpcId: id }) => {
+ const nameTag = tags.find(({ Key: key }) => key === 'Name');
+
+ return nameTag ? nameTag.Value : id;
+};
+
+export const DEFAULT_REGION = 'us-east-2';
+
+export const setAWSConfig = ({ awsCredentials }) => {
+ AWS.config = {
+ ...awsCredentials,
+ region: DEFAULT_REGION,
+ };
+};
+
+export const fetchRoles = () => {
+ const iam = new IAM();
+
+ return iam
+ .listRoles()
+ .promise()
+ .then(({ Roles: roles }) => roles.map(({ RoleName: name, Arn: value }) => ({ name, value })));
+};
+
+export const fetchRegions = () => {
+ const ec2 = new EC2();
+
+ return ec2
+ .describeRegions()
+ .promise()
+ .then(({ Regions: regions }) =>
+ regions.map(({ RegionName: name }) => ({
+ name,
+ value: name,
})),
);
- },
- fetchVpcs({ region }) {
- return axios.get(apiPaths.getVpcsPath, { params: { region } }).then(({ data: { vpcs } }) =>
- vpcs.map(({ vpc_id }) => ({
- value: vpc_id,
- name: vpc_id,
+};
+
+export const fetchKeyPairs = ({ region }) => {
+ const ec2 = new EC2({ region });
+
+ return ec2
+ .describeKeyPairs()
+ .promise()
+ .then(({ KeyPairs: keyPairs }) => keyPairs.map(({ KeyName: name }) => ({ name, value: name })));
+};
+
+export const fetchVpcs = ({ region }) => {
+ const ec2 = new EC2({ region });
+
+ return ec2
+ .describeVpcs()
+ .promise()
+ .then(({ Vpcs: vpcs }) =>
+ vpcs.map(vpc => ({
+ value: vpc.VpcId,
+ name: lookupVpcName(vpc),
})),
);
- },
- fetchSubnets({ vpc, region }) {
- return axios
- .get(apiPaths.getSubnetsPath, { params: { vpc_id: vpc, region } })
- .then(({ data: { subnets } }) =>
- subnets.map(({ subnet_id }) => ({ name: subnet_id, value: subnet_id })),
- );
- },
- fetchSecurityGroups({ vpc, region }) {
- return axios
- .get(apiPaths.getSecurityGroupsPath, { params: { vpc_id: vpc, region } })
- .then(({ data: { security_groups: securityGroups } }) =>
- securityGroups.map(({ group_name: name, group_id: value }) => ({ name, value })),
- );
- },
- fetchInstanceTypes() {
- return axios
- .get(apiPaths.getInstanceTypesPath)
- .then(({ data: { instance_types: instanceTypes } }) =>
- instanceTypes.map(({ instance_type_name }) => ({
- name: instance_type_name,
- value: instance_type_name,
- })),
- );
- },
-});
+};
+
+export const fetchSubnets = ({ vpc, region }) => {
+ const ec2 = new EC2({ region });
+
+ return ec2
+ .describeSubnets({
+ Filters: [
+ {
+ Name: 'vpc-id',
+ Values: [vpc],
+ },
+ ],
+ })
+ .promise()
+ .then(({ Subnets: subnets }) => subnets.map(({ SubnetId: id }) => ({ value: id, name: id })));
+};
+
+export const fetchSecurityGroups = ({ region, vpc }) => {
+ const ec2 = new EC2({ region });
+
+ return ec2
+ .describeSecurityGroups({
+ Filters: [
+ {
+ Name: 'vpc-id',
+ Values: [vpc],
+ },
+ ],
+ })
+ .promise()
+ .then(({ SecurityGroups: securityGroups }) =>
+ securityGroups.map(({ GroupName: name, GroupId: value }) => ({ name, value })),
+ );
+};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
index 72f15263a8f..e96e6d6e4f8 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js
@@ -1,6 +1,8 @@
import * as types from './mutation_types';
+import { setAWSConfig } from '../services/aws_services_facade';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
const getErrorMessage = data => {
const errorKey = Object.keys(data)[0];
@@ -28,7 +30,7 @@ export const createRole = ({ dispatch, state: { createRolePath } }, payload) =>
role_arn: payload.roleArn,
role_external_id: payload.externalId,
})
- .then(() => dispatch('createRoleSuccess'))
+ .then(({ data }) => dispatch('createRoleSuccess', convertObjectPropsToCamelCase(data)))
.catch(error => dispatch('createRoleError', { error }));
};
@@ -36,7 +38,8 @@ export const requestCreateRole = ({ commit }) => {
commit(types.REQUEST_CREATE_ROLE);
};
-export const createRoleSuccess = ({ commit }) => {
+export const createRoleSuccess = ({ commit }, awsCredentials) => {
+ setAWSConfig({ awsCredentials });
commit(types.CREATE_ROLE_SUCCESS);
};
@@ -117,9 +120,3 @@ export const setInstanceType = ({ commit }, payload) => {
export const setNodeCount = ({ commit }, payload) => {
commit(types.SET_NODE_COUNT, payload);
};
-
-export const signOut = ({ commit, state: { signOutPath } }) =>
- axios
- .delete(signOutPath)
- .then(() => commit(types.SIGN_OUT))
- .catch(({ response: { data } }) => createFlash(getErrorMessage(data)));
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/index.js b/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/index.js
index 07a5821c47d..0b19589215c 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/index.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/index.js
@@ -3,11 +3,11 @@ import actions from './actions';
import mutations from './mutations';
import state from './state';
-const createStore = fetchFn => ({
+const createStore = ({ fetchFn, initialState }) => ({
actions: actions(fetchFn),
getters,
mutations,
- state: state(),
+ state: Object.assign(state(), initialState || {}),
});
export default createStore;
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
index 5982fc8a2fd..09fd560240d 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js
@@ -6,12 +6,17 @@ import state from './state';
import clusterDropdownStore from './cluster_dropdown';
-import awsServicesFactory from '../services/aws_services_facade';
+import {
+ fetchRoles,
+ fetchRegions,
+ fetchKeyPairs,
+ fetchVpcs,
+ fetchSubnets,
+ fetchSecurityGroups,
+} from '../services/aws_services_facade';
-const createStore = ({ initialState, apiPaths }) => {
- const awsServices = awsServicesFactory(apiPaths);
-
- return new Vuex.Store({
+const createStore = ({ initialState }) =>
+ new Vuex.Store({
actions,
getters,
mutations,
@@ -19,34 +24,33 @@ const createStore = ({ initialState, apiPaths }) => {
modules: {
roles: {
namespaced: true,
- ...clusterDropdownStore(awsServices.fetchRoles),
+ ...clusterDropdownStore({ fetchFn: fetchRoles }),
},
regions: {
namespaced: true,
- ...clusterDropdownStore(awsServices.fetchRegions),
+ ...clusterDropdownStore({ fetchFn: fetchRegions }),
},
keyPairs: {
namespaced: true,
- ...clusterDropdownStore(awsServices.fetchKeyPairs),
+ ...clusterDropdownStore({ fetchFn: fetchKeyPairs }),
},
vpcs: {
namespaced: true,
- ...clusterDropdownStore(awsServices.fetchVpcs),
+ ...clusterDropdownStore({ fetchFn: fetchVpcs }),
},
subnets: {
namespaced: true,
- ...clusterDropdownStore(awsServices.fetchSubnets),
+ ...clusterDropdownStore({ fetchFn: fetchSubnets }),
},
securityGroups: {
namespaced: true,
- ...clusterDropdownStore(awsServices.fetchSecurityGroups),
+ ...clusterDropdownStore({ fetchFn: fetchSecurityGroups }),
},
instanceTypes: {
namespaced: true,
- ...clusterDropdownStore(awsServices.fetchInstanceTypes),
+ ...clusterDropdownStore({ initialState: { items: initialState.instanceTypes } }),
},
},
});
-};
export default createStore;
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
index f9204cc2207..9dee6abae5f 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js
@@ -13,7 +13,6 @@ export const SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER';
export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE';
export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS';
export const CREATE_ROLE_ERROR = 'CREATE_ROLE_ERROR';
-export const SIGN_OUT = 'SIGN_OUT';
export const REQUEST_CREATE_CLUSTER = 'REQUEST_CREATE_CLUSTER';
export const CREATE_CLUSTER_SUCCESS = 'CREATE_CLUSTER_SUCCESS';
export const CREATE_CLUSTER_ERROR = 'CREATE_CLUSTER_ERROR';
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
index aa04c8f7079..c331d27d255 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js
@@ -60,7 +60,4 @@ export default {
state.isCreatingCluster = false;
state.createClusterError = error;
},
- [types.SIGN_OUT](state) {
- state.hasCredentials = false;
- },
};
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
index 2e3a05a9187..20434dcce98 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
+++ b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js
@@ -12,6 +12,8 @@ export default () => ({
accountId: '',
externalId: '',
+ roleArn: '',
+
clusterName: '',
environmentScope: '*',
kubernetesVersion,
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 33ae778769a..1ed8da57927 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -228,10 +228,6 @@ class ApplicationController < ActionController::Base
end
end
- def respond_201
- head :created
- end
-
def respond_422
head :unprocessable_entity
end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 0295f36732c..f4b74b14c0b 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -8,7 +8,7 @@ class Clusters::ClustersController < Clusters::BaseController
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, :revoke_aws_role, :aws_proxy]
+ before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role]
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy, :clear_cache]
before_action :update_applications_status, only: [:cluster_status]
@@ -42,6 +42,7 @@ class Clusters::ClustersController < Clusters::BaseController
if params[:provider] == 'aws'
@aws_role = current_user.aws_role || Aws::Role.new
@aws_role.ensure_role_external_id!
+ @instance_types = load_instance_types.to_json
elsif params[:provider] == 'gcp'
redirect_to @authorize_url if @authorize_url && !@valid_gcp_token
@@ -145,21 +146,9 @@ class Clusters::ClustersController < Clusters::BaseController
end
def authorize_aws_role
- role = current_user.build_aws_role(create_role_params)
-
- 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
+ response = Clusters::Aws::AuthorizeRoleService.new(
+ current_user,
+ params: aws_role_params
).execute
render json: response.body, status: response.status
@@ -268,7 +257,7 @@ class Clusters::ClustersController < Clusters::BaseController
)
end
- def create_role_params
+ def aws_role_params
params.require(:cluster).permit(:role_arn, :role_external_id)
end
@@ -314,6 +303,19 @@ class Clusters::ClustersController < Clusters::BaseController
end
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 load_instance_types
+ stack_template = File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
+ instance_types = YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues')
+
+ instance_types.map { |type| Hash(name: type, value: type) }
+ end
+
def update_applications_status
@cluster.applications.each(&:schedule_status_update)
end
diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb
index 56a66dd38db..ba21ccfb169 100644
--- a/app/controllers/projects/error_tracking_controller.rb
+++ b/app/controllers/projects/error_tracking_controller.rb
@@ -77,8 +77,10 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
return if handle_errors(result)
+ result_with_syntax_highlight = Gitlab::ErrorTracking::StackTraceHighlightDecorator.decorate(result[:latest_event])
+
render json: {
- error: serialize_error_event(result[:latest_event])
+ error: serialize_error_event(result_with_syntax_highlight)
}
end
diff --git a/app/models/clusters/providers/aws.rb b/app/models/clusters/providers/aws.rb
index 78eb75ddcc0..faf587fb83d 100644
--- a/app/models/clusters/providers/aws.rb
+++ b/app/models/clusters/providers/aws.rb
@@ -8,9 +8,11 @@ module Clusters
self.table_name = 'cluster_providers_aws'
+ DEFAULT_REGION = 'us-east-1'
+
belongs_to :cluster, inverse_of: :provider_aws, class_name: 'Clusters::Cluster'
- default_value_for :region, 'us-east-1'
+ default_value_for :region, DEFAULT_REGION
default_value_for :num_nodes, 3
default_value_for :instance_type, 'm5.large'
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 19216281e48..793ea3c29c3 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -9,7 +9,7 @@ class DeployKey < Key
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
scope :are_public, -> { where(public: true) }
- scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, :namespace] }) }
+ scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, namespace: :route] }) }
ignore_column :can_push, remove_after: '2019-12-15', remove_with: '12.6'
@@ -24,7 +24,7 @@ class DeployKey < Key
end
def almost_orphaned?
- self.deploy_keys_projects.count == 1
+ self.deploy_keys_projects.size == 1
end
def destroyed_when_orphaned?
@@ -44,7 +44,11 @@ class DeployKey < Key
end
def deploy_keys_project_for(project)
- deploy_keys_projects.find_by(project: project)
+ if association(:deploy_keys_projects).loaded?
+ deploy_keys_projects.find { |dkp| dkp.project_id.eql?(project&.id) }
+ else
+ deploy_keys_projects.find_by(project: project)
+ end
end
def projects_with_write_access
diff --git a/app/models/user.rb b/app/models/user.rb
index 6a29de20d86..698848c5b16 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1001,7 +1001,7 @@ class User < ApplicationRecord
end
def project_deploy_keys
- DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
+ @project_deploy_keys ||= DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
end
def highest_role
diff --git a/app/policies/deploy_key_policy.rb b/app/policies/deploy_key_policy.rb
index 7f0ec011e79..b117bb57921 100644
--- a/app/policies/deploy_key_policy.rb
+++ b/app/policies/deploy_key_policy.rb
@@ -3,10 +3,7 @@
class DeployKeyPolicy < BasePolicy
with_options scope: :subject, score: 0
condition(:private_deploy_key) { @subject.private? }
-
- # rubocop: disable CodeReuse/ActiveRecord
- condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) }
- # rubocop: enable CodeReuse/ActiveRecord
+ condition(:has_deploy_key) { @user.project_deploy_keys.any? { |pdk| pdk.id.eql?(@subject.id) } }
rule { anonymous }.prevent_all
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 7677e6f026f..6b1d82e7557 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -29,18 +29,10 @@ 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
diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb
index 34d3f347689..0c267fd5735 100644
--- a/app/presenters/instance_clusterable_presenter.rb
+++ b/app/presenters/instance_clusterable_presenter.rb
@@ -67,16 +67,6 @@ class InstanceClusterablePresenter < ClusterablePresenter
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/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb
index 9bb7fe13593..66211d02696 100644
--- a/app/presenters/projects/settings/deploy_keys_presenter.rb
+++ b/app/presenters/projects/settings/deploy_keys_presenter.rb
@@ -3,6 +3,8 @@
module Projects
module Settings
class DeployKeysPresenter < Gitlab::View::Presenter::Simple
+ include Gitlab::Utils::StrongMemoize
+
presents :project
delegate :size, to: :enabled_keys, prefix: true
delegate :size, to: :available_project_keys, prefix: true
@@ -13,37 +15,45 @@ module Projects
end
def enabled_keys
- project.deploy_keys
+ strong_memoize(:enabled_keys) do
+ project.deploy_keys.with_projects
+ end
end
def available_keys
- current_user
- .accessible_deploy_keys
- .id_not_in(enabled_keys.select(:id))
- .with_projects
+ strong_memoize(:available_keys) do
+ current_user
+ .accessible_deploy_keys
+ .id_not_in(enabled_keys.select(:id))
+ .with_projects
+ end
end
def available_project_keys
- current_user
- .project_deploy_keys
- .id_not_in(enabled_keys.select(:id))
- .with_projects
+ strong_memoize(:available_project_keys) do
+ current_user
+ .project_deploy_keys
+ .id_not_in(enabled_keys.select(:id))
+ .with_projects
+ end
end
def available_public_keys
- DeployKey
- .are_public
- .id_not_in(enabled_keys.select(:id))
- .id_not_in(available_project_keys.select(:id))
- .with_projects
+ strong_memoize(:available_public_keys) do
+ DeployKey
+ .are_public
+ .id_not_in(enabled_keys.select(:id))
+ .id_not_in(available_project_keys.select(:id))
+ .with_projects
+ end
end
def as_json
serializer = DeployKeySerializer.new # rubocop: disable CodeReuse/Serializer
- opts = { user: current_user, project: project }
+ opts = { user: current_user, project: project, readable_project_ids: readable_project_ids }
{
- enabled_keys: serializer.represent(enabled_keys.with_projects, opts),
+ enabled_keys: serializer.represent(enabled_keys, opts),
available_project_keys: serializer.represent(available_project_keys, opts),
public_keys: serializer.represent(available_public_keys, opts)
}
@@ -56,6 +66,26 @@ module Projects
def form_partial_path
'projects/deploy_keys/form'
end
+
+ private
+
+ # Caching all readable project ids for the user that are associated with the queried deploy keys
+ def readable_project_ids
+ strong_memoize(:readable_projects_by_id) do
+ Set.new(user_readable_project_ids)
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def user_readable_project_ids
+ project_ids = (available_keys + available_project_keys + available_public_keys)
+ .flat_map { |deploy_key| deploy_key.deploy_keys_projects.map(&:project_id) }
+ .compact
+ .uniq
+
+ current_user.authorized_projects(Gitlab::Access::GUEST).id_in(project_ids).pluck(:id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb
index 9a558d12bec..2682a47fbaa 100644
--- a/app/serializers/deploy_key_entity.rb
+++ b/app/serializers/deploy_key_entity.rb
@@ -11,8 +11,7 @@ class DeployKeyEntity < Grape::Entity
expose :updated_at
expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key|
deploy_key.deploy_keys_projects.select do |deploy_key_project|
- !deploy_key_project.project&.pending_delete? &&
- Ability.allowed?(options[:user], :read_project, deploy_key_project.project)
+ !deploy_key_project.project&.pending_delete? && (allowed_to_read_project?(deploy_key_project.project) || options[:user].admin?)
end
end
expose :can_edit
@@ -23,4 +22,12 @@ class DeployKeyEntity < Grape::Entity
Ability.allowed?(options[:user], :update_deploy_key, object) ||
Ability.allowed?(options[:user], :update_deploy_keys_project, object.deploy_keys_project_for(options[:project]))
end
+
+ def allowed_to_read_project?(project)
+ if options[:readable_project_ids]
+ options[:readable_project_ids].include?(project.id)
+ else
+ Ability.allowed?(options[:user], :read_project, project)
+ end
+ end
end
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
diff --git a/app/services/clusters/kubernetes/kubernetes.rb b/app/services/clusters/kubernetes.rb
index 59cb1c4b3a9..59cb1c4b3a9 100644
--- a/app/services/clusters/kubernetes/kubernetes.rb
+++ b/app/services/clusters/kubernetes.rb
diff --git a/app/views/clusters/clusters/aws/_new.html.haml b/app/views/clusters/clusters/aws/_new.html.haml
index 795b80bfb6f..d89e6965dac 100644
--- a/app/views/clusters/clusters/aws/_new.html.haml
+++ b/app/views/clusters/clusters/aws/_new.html.haml
@@ -5,19 +5,12 @@
- 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,
+ 'role-arn' => @aws_role.role_arn,
+ 'instance-types' => @instance_types,
'kubernetes-integration-help-path' => help_page_path('user/project/clusters/index'),
'account-and-external-ids-help-path' => help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'eks-cluster'),
'create-role-arn-help-path' => help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'eks-cluster'),
- 'external-link-icon' => icon('external-link'),
- 'has-credentials' => @aws_role.role_arn.present?.to_s } }
+ 'external-link-icon' => icon('external-link') } }