diff --git a/addons/api/addon/generated/models/host-catalog.js b/addons/api/addon/generated/models/host-catalog.js
index a300bf59d..30ada833f 100644
--- a/addons/api/addon/generated/models/host-catalog.js
+++ b/addons/api/addon/generated/models/host-catalog.js
@@ -66,7 +66,6 @@ export default class GeneratedHostCatalogModel extends BaseModel {
// AWS specific
@attr('string', {
- for: 'plugin',
description:
'An expression used to filter the workers that have network access to a service that is hosting the external object store.',
})
@@ -78,6 +77,7 @@ export default class GeneratedHostCatalogModel extends BaseModel {
})
region;
+ // AWS static credentials
@attr('string', {
isNestedSecret: true,
description: '',
@@ -90,6 +90,31 @@ export default class GeneratedHostCatalogModel extends BaseModel {
})
secret_access_key;
+ // AWS dynamic credentials
+ @attr('string', {
+ isNestedAttribute: true,
+ description: 'The role ARN to use.',
+ })
+ role_arn;
+
+ @attr('string', {
+ isNestedAttribute: true,
+ description: 'The role external ID to use.',
+ })
+ role_external_id;
+
+ @attr('string', {
+ isNestedAttribute: true,
+ description: 'The role session to use.',
+ })
+ role_session_name;
+
+ @attr('object-as-array', {
+ isNestedAttribute: true,
+ description: 'The role tags to use.',
+ })
+ role_tags;
+
// Azure specific
@attr('string', {
diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js
index 4ede285f2..4a556035a 100644
--- a/addons/api/addon/models/host-catalog.js
+++ b/addons/api/addon/models/host-catalog.js
@@ -19,9 +19,18 @@ export const TYPES_HOST_CATALOG_PLUGIN = [
TYPE_HOST_CATALOG_PLUGIN_AZURE,
];
+export const TYPE_CREDENTIAL_STATIC = 'static-credential';
+export const TYPE_CREDENTIAL_DYNAMIC = 'dynamic-credential';
+
+export const TYPES_CREDENTIALS = Object.freeze([
+ TYPE_CREDENTIAL_STATIC,
+ TYPE_CREDENTIAL_DYNAMIC,
+]);
+
export default class HostCatalogModel extends GeneratedHostCatalogModel {
// =attributes
+ #credentialType;
/**
* True if the host catalog is static.
* @type {boolean}
@@ -38,6 +47,29 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
return this.type === 'plugin';
}
+ /**
+ * Gets a value from credentialType
+ * @type {string}
+ */
+ get credentialType() {
+ if (!this.#credentialType) {
+ if (this.role_arn) {
+ this.#credentialType = TYPE_CREDENTIAL_DYNAMIC;
+ } else {
+ this.#credentialType = TYPE_CREDENTIAL_STATIC;
+ }
+ }
+ return this.#credentialType;
+ }
+
+ /**
+ * Sets a value to credentialType
+ * @type {string}
+ */
+ set credentialType(type) {
+ this.#credentialType = type;
+ }
+
/**
* True if the host catalog is an unknown type.
* @type {boolean}
diff --git a/addons/api/addon/serializers/host-catalog.js b/addons/api/addon/serializers/host-catalog.js
index 0f978ca67..2e3163df6 100644
--- a/addons/api/addon/serializers/host-catalog.js
+++ b/addons/api/addon/serializers/host-catalog.js
@@ -4,30 +4,53 @@
*/
import ApplicationSerializer from './application';
+import {
+ TYPE_HOST_CATALOG_PLUGIN_AWS,
+ TYPE_HOST_CATALOG_PLUGIN_AZURE,
+} from '../models/host-catalog';
-const fieldsByType = {
- aws: [
- 'disable_credential_rotation',
- 'region',
+// These fields are Azure specific
+const azureFields = [
+ 'disable_credential_rotation',
+ 'tenant_id',
+ 'client_id',
+ 'subscription_id',
+ 'secret_id',
+ 'secret_value',
+];
+
+// These fields are AWS specific, AWS provider now has two credentialTypes: static and dynamic [not to be confused with static and dynamic host-catalogs]
+const AWSfieldsWithCredentialType = {
+ 'static-credential': [
'access_key_id',
'secret_access_key',
+ 'region',
+ 'disable_credential_rotation',
'worker_filter',
],
- azure: [
+ 'dynamic-credential': [
+ 'role_arn',
+ 'role_external_id',
+ 'role_session_name',
+ 'role_tags',
+ 'region',
'disable_credential_rotation',
- 'tenant_id',
- 'client_id',
- 'subscription_id',
- 'secret_id',
- 'secret_value',
+ 'worker_filter',
],
};
export default class HostCatalogSerializer extends ApplicationSerializer {
- // =methods
serialize(snapshot) {
+ const { compositeType } = snapshot.record;
const serialized = super.serialize(...arguments);
- switch (snapshot.record.compositeType) {
+
+ // This is a hack to remove the worker_filter field from the serialized data if the compositeType is not AWS
+ // Ideally we should refactor the model to include the compositeType using `for` attribute
+ if (compositeType !== TYPE_HOST_CATALOG_PLUGIN_AWS) {
+ delete serialized.worker_filter;
+ }
+
+ switch (compositeType) {
case 'static':
return this.serializeStatic(...arguments);
default:
@@ -37,31 +60,28 @@ export default class HostCatalogSerializer extends ApplicationSerializer {
serializeAttribute(snapshot, json, key, attribute) {
const value = super.serializeAttribute(...arguments);
- const { isPlugin, compositeType } = snapshot.record;
+ const { compositeType, credentialType, isPlugin } = snapshot.record;
const { options } = attribute;
- // Delete any fields that don't belong to the compositeType
- if (
- isPlugin &&
- json &&
- options?.for &&
- !options.for.includes(compositeType) &&
- !fieldsByType[compositeType].includes(key)
- ) {
- delete json[key];
- }
+ const fields =
+ compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE
+ ? azureFields
+ : AWSfieldsWithCredentialType[credentialType];
// Delete any nested attribute fields that don't belong to the record type
if (isPlugin && options.isNestedAttribute && json.attributes) {
- if (!fieldsByType[compositeType].includes(key)) {
+ if (!fields.includes(key)) {
delete json.attributes[key];
}
}
// Delete any secret fields that don't belong to the record type
if (isPlugin && options.isNestedSecret && json.secrets) {
- if (!fieldsByType[compositeType].includes(key)) {
+ if (!fields.includes(key)) {
delete json.secrets[key];
+ if (json['secrets'] && Object.keys(json.secrets).length === 0) {
+ delete json.secrets;
+ }
}
}
return value;
@@ -72,6 +92,7 @@ export default class HostCatalogSerializer extends ApplicationSerializer {
// Delete unnecessary fields for static host-catalog
delete serialized.attributes;
delete serialized.secrets;
+ delete serialized.worker_filter;
return serialized;
}
}
diff --git a/addons/api/tests/unit/serializers/host-catalog-test.js b/addons/api/tests/unit/serializers/host-catalog-test.js
index c38352d32..b8b1b7530 100644
--- a/addons/api/tests/unit/serializers/host-catalog-test.js
+++ b/addons/api/tests/unit/serializers/host-catalog-test.js
@@ -5,6 +5,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
+import {
+ TYPE_HOST_CATALOG_PLUGIN_AWS,
+ TYPE_CREDENTIAL_STATIC,
+ TYPE_CREDENTIAL_DYNAMIC,
+} from 'api/models/host-catalog';
module('Unit | Serializer | host catalog', function (hooks) {
setupTest(hooks);
@@ -31,15 +36,104 @@ module('Unit | Serializer | host catalog', function (hooks) {
assert.deepEqual(record.serialize(), expectedResult);
});
- test('it serializes a new aws plugin as expected, ignoring azure fields', async function (assert) {
+ test('it serializes a new aws static plugin as expected', async function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
- compositeType: 'aws',
+ compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
+ credentialType: TYPE_CREDENTIAL_STATIC,
name: 'AWS',
description: 'this is a Aws plugin host-catalog',
disable_credential_rotation: true,
worker_filter: 'workerfilter',
// these are AWS fields and should be included
+ region: 'west',
+ access_key_id: 'foobars',
+ secret_access_key: 'testing',
+ // these are Azure fields and should be excluded
+ tenant_id: 'a1b2c3',
+ client_id: 'a1b2c3',
+ subscription_id: 'a1b2c3',
+ secret_id: 'a1b2c3',
+ secret_value: 'a1b2c3',
+ role_arn: 'test',
+ role_external_id: 'test',
+ role_session_name: 'test',
+ role_tags: [
+ { key: 'Project', value: 'Automation' },
+ { key: 'foo', value: 'bar' },
+ ],
+ });
+ const expectedResult = {
+ name: 'AWS',
+ description: 'this is a Aws plugin host-catalog',
+ type: 'plugin',
+ worker_filter: 'workerfilter',
+ attributes: {
+ disable_credential_rotation: true,
+ region: 'west',
+ },
+ secrets: {
+ access_key_id: 'foobars',
+ secret_access_key: 'testing',
+ },
+ };
+ assert.deepEqual(record.serialize(), expectedResult);
+ });
+
+ test('it serializes a new aws dynamic plugin as expected', async function (assert) {
+ const store = this.owner.lookup('service:store');
+ const record = store.createRecord('host-catalog', {
+ compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
+ name: 'AWS',
+ credentialType: TYPE_CREDENTIAL_DYNAMIC,
+ description: 'this is a Aws plugin host-catalog',
+ disable_credential_rotation: true,
+ worker_filter: 'workerfilter',
+ // these are AWS fields and should be included
+ region: 'west',
+ access_key_id: 'foobars',
+ secret_access_key: 'testing',
+ // these are Azure fields and should be excluded
+ tenant_id: 'a1b2c3',
+ client_id: 'a1b2c3',
+ subscription_id: 'a1b2c3',
+ secret_id: 'a1b2c3',
+ secret_value: 'a1b2c3',
+ role_arn: 'test',
+ role_external_id: 'test',
+ role_session_name: 'test',
+ role_tags: [
+ { key: 'Project', value: 'Automation' },
+ { key: 'foo', value: 'bar' },
+ ],
+ });
+ const expectedResult = {
+ name: 'AWS',
+ description: 'this is a Aws plugin host-catalog',
+ type: 'plugin',
+ worker_filter: 'workerfilter',
+ attributes: {
+ disable_credential_rotation: true,
+ region: 'west',
+ role_external_id: 'test',
+ role_session_name: 'test',
+ role_arn: 'test',
+ role_tags: { Project: 'Automation', foo: 'bar' },
+ },
+ };
+ assert.deepEqual(record.serialize(), expectedResult);
+ });
+
+ test('it serializes a new aws static plugin as expected, ignoring azure fields', async function (assert) {
+ const store = this.owner.lookup('service:store');
+ const record = store.createRecord('host-catalog', {
+ compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
+ name: 'AWS',
+ credentialType: TYPE_CREDENTIAL_STATIC,
+ description: 'this is a Aws plugin host-catalog',
+ disable_credential_rotation: true,
+ worker_filter: 'workerfilter',
+ // these are AWS fields and should be included
region: 'spain',
access_key_id: 'foobars',
secret_access_key: 'testing',
@@ -109,6 +203,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
data: {
id: '1',
type: 'host-catalog',
+ credentialType: 'static',
attributes: {
type: 'plugin',
name: 'aws',
diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml
index 75bf4d0af..6b2e80bd5 100644
--- a/addons/core/translations/resources/en-us.yaml
+++ b/addons/core/translations/resources/en-us.yaml
@@ -218,6 +218,17 @@ host-catalog:
aws: AWS
azure: Azure
plugin: Dynamic
+ credential:
+ label: Credential type
+ help: Select the credential type that you want to use.
+ static-credential:
+ edit: Static Credentials
+ label: Use an access key (Static Credentials)
+ help: Use an access key that you have generated in AWS to use with this AWS Host Catalog.
+ dynamic-credential:
+ edit: Dynamic Credentials
+ label: Use Assume Role (Dynamic Credentials)
+ help: Specify an Assume Role used to generate credentials to use with this AWS Host Catalog.
form:
aws_region:
label: AWS Region
diff --git a/ui/admin/app/components/form/host-catalog/aws/index.hbs b/ui/admin/app/components/form/host-catalog/aws/index.hbs
index b2a0891cb..4b47803cc 100644
--- a/ui/admin/app/components/form/host-catalog/aws/index.hbs
+++ b/ui/admin/app/components/form/host-catalog/aws/index.hbs
@@ -19,12 +19,18 @@
{{on 'input' (set-from-event @model 'name')}}
as |F|
>
- {{t 'form.name.label'}}
- {{t 'form.name.help'}}
+
+ {{t 'form.name.label'}}
+
+
+ {{t 'form.name.help'}}
+
{{#if @model.errors.name}}
{{#each @model.errors.name as |error|}}
- {{error.message}}
+
+ {{error.message}}
+
{{/each}}
{{/if}}
@@ -38,21 +44,28 @@
disabled={{form.disabled}}
as |F|
>
- {{t 'form.description.label'}}
- {{t 'form.description.help'}}
+
+ {{t 'form.description.label'}}
+
+
+ {{t 'form.description.help'}}
+
{{#if @model.errors.description}}
{{#each @model.errors.description as |error|}}
- {{error.message}}
+
+ {{error.message}}
+
{{/each}}
{{/if}}
{{#if @model.isNew}}
-
- {{t 'form.type.label'}}
+
+ {{t 'form.type.label'}}
+
{{#each this.hostCatalogTypes as |hostCatalogType|}}
- {{t
- (concat 'resources.host-catalog.types.' hostCatalogType)
- }}
- {{t
- (concat 'resources.host-catalog.help.' hostCatalogType)
- }}
+
+ {{t (concat 'resources.host-catalog.types.' hostCatalogType)}}
+
+
+ {{t (concat 'resources.host-catalog.help.' hostCatalogType)}}
+
{{/each}}
- {{t 'titles.provider'}}
- {{t 'titles.choose-a-provider'}}
- {{t 'descriptions.choose-a-provider'}}
+
+ {{t 'titles.provider'}}
+
+
+ {{t 'titles.choose-a-provider'}}
+
+
+ {{t 'descriptions.choose-a-provider'}}
+
{{#each-in this.mapResourceTypeWithIcon as |pluginType icon|}}
- {{t
- (concat 'resources.host-catalog.types.' pluginType)
- }}
+
+ {{t (concat 'resources.host-catalog.types.' pluginType)}}
+
-
{{/each-in}}
@@ -98,8 +116,11 @@
readonly={{true}}
as |F|
>
- {{t 'titles.provider'}}
- {{t 'descriptions.provider'}}
+
+ {{t 'titles.provider'}}
+
+
+ {{t 'descriptions.provider'}}
{{/if}}
@@ -112,7 +133,9 @@
{{on 'input' (set-from-event @model 'region')}}
as |F|
>
- {{t 'resources.host-catalog.form.aws_region.label'}}
+
+ {{t 'resources.host-catalog.form.aws_region.label'}}
+
{{t 'resources.host-catalog.form.aws_region.help'}}
@@ -121,39 +144,225 @@
-
- {{t 'form.access_key_id.label'}}
-
- {{t 'form.access_key_id.help'}}
-
- {{t 'actions.learn-more'}}
-
-
-
+ {{#if @model.isNew}}
+
+
+ {{t 'resources.host-catalog.types.credential.label'}}
+
+
+ {{t 'resources.host-catalog.types.credential.help'}}
+
-
- {{t 'form.secret_access_key.label'}}
-
- {{t 'form.secret_access_key.help'}}
-
- {{t 'actions.learn-more'}}
-
-
-
+ {{#each this.credentials as |credentialType|}}
+
+
+ {{t
+ (concat
+ 'resources.host-catalog.types.credential.'
+ credentialType
+ '.label'
+ )
+ }}
+
+
+ {{t
+ (concat
+ 'resources.host-catalog.types.credential.'
+ credentialType
+ '.help'
+ )
+ }}
+
+
+ {{/each}}
+
+ {{else}}
+
+
+ {{t 'resources.host-catalog.types.credential.label'}}
+
+
+
+
+
+ {{/if}}
+
+ {{#if this.showDynamicCredentials}}
+ {{! role_arn }}
+
+
+ {{t 'resources.storage-bucket.form.role_arn.label'}}
+
+
+ {{t 'resources.storage-bucket.form.role_arn.help'}}
+
+ {{#if @model.errors.role_arn}}
+
+ {{#each @model.errors.role_arn as |error|}}
+
+ {{error.message}}
+
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! role_external_id }}
+
+
+ {{t 'resources.storage-bucket.form.role_external_id.label'}}
+
+
+ {{t 'resources.storage-bucket.form.role_external_id.help'}}
+
+ {{#if @model.errors.role_external_id}}
+
+ {{#each @model.errors.role_external_id as |error|}}
+
+ {{error.message}}
+
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! role_session_name }}
+
+
+ {{t 'resources.storage-bucket.form.role_session_name.label'}}
+
+
+ {{t 'resources.storage-bucket.form.role_session_name.help'}}
+
+ {{#if @model.errors.role_session_name}}
+
+ {{#each @model.errors.role_session_name as |error|}}
+
+ {{error.message}}
+
+ {{/each}}
+
+ {{/if}}
+
+
+ {{! role_tags }}
+
+ <:fieldset as |F|>
+
+ {{t 'resources.storage-bucket.form.role_tags.label'}}
+
+
+ {{t 'resources.storage-bucket.form.role_tags.help'}}
+
+
+ {{#if @model.errors.role_tags}}
+
+ {{#each @model.errors.role_tags as |error|}}
+
+ {{error.message}}
+
+ {{/each}}
+
+ {{/if}}
+
+ <:field as |F|>
+
+ <:key as |K|>
+
+
+ <:value as |V|>
+
+
+
+
+
+
+ {{else}}
+
+
+ {{t 'form.access_key_id.label'}}
+
+
+ {{t 'form.access_key_id.help'}}
+
+ {{t 'actions.learn-more'}}
+
+
+
+
+
+
+ {{t 'form.secret_access_key.label'}}
+
+
+ {{t 'form.secret_access_key.help'}}
+
+ {{t 'actions.learn-more'}}
+
+
+
+ {{/if}}
{{#each @model.errors.worker_filter as |error|}}
- {{error.message}}
+
+ {{error.message}}
+
{{/each}}
{{/if}}
@@ -188,7 +399,9 @@
{{on 'change' (fn this.toggleDisableCredentialRotation @model)}}
as |F|
>
- {{t 'form.disable_credential_rotation.label'}}
+
+ {{t 'form.disable_credential_rotation.label'}}
+
diff --git a/ui/admin/app/components/form/host-catalog/aws/index.js b/ui/admin/app/components/form/host-catalog/aws/index.js
index 166e0c2f0..af42090d0 100644
--- a/ui/admin/app/components/form/host-catalog/aws/index.js
+++ b/ui/admin/app/components/form/host-catalog/aws/index.js
@@ -8,7 +8,10 @@ import { action } from '@ember/object';
import {
TYPES_HOST_CATALOG,
TYPES_HOST_CATALOG_PLUGIN,
+ TYPES_CREDENTIALS,
+ TYPE_CREDENTIAL_DYNAMIC,
} from 'api/models/host-catalog';
+import { tracked } from '@glimmer/tracking';
//Note: this is a temporary solution till we have resource type helper in place
const icons = ['aws-color', 'azure-color'];
@@ -16,6 +19,21 @@ const icons = ['aws-color', 'azure-color'];
export default class FormHostCatalogAwsComponent extends Component {
// =properties
hostCatalogTypes = TYPES_HOST_CATALOG;
+
+ // =attributes
+ @tracked selectedCredentialType = this.args.model.credentialType;
+
+ @tracked showDynamicCredentials =
+ this.args.model.credentialType === TYPE_CREDENTIAL_DYNAMIC;
+
+ /**
+ * returns an array of available credential types
+ * @type {array}
+ */
+ get credentials() {
+ return TYPES_CREDENTIALS;
+ }
+
/**
* maps resource type with icon
* @type {object}
@@ -32,4 +50,19 @@ export default class FormHostCatalogAwsComponent extends Component {
toggleDisableCredentialRotation(model) {
model.disable_credential_rotation = !model.disable_credential_rotation;
}
+
+ /**
+ * Allows to update the credential type
+ * @param type {string}
+ */
+ @action
+ updateCredentialTypeSelection(type) {
+ this.selectedCredentialType = type;
+ if (type === TYPE_CREDENTIAL_DYNAMIC) {
+ this.showDynamicCredentials = true;
+ } else {
+ this.showDynamicCredentials = false;
+ }
+ this.args.changeCredentialType(type);
+ }
}
diff --git a/ui/admin/app/components/form/host-catalog/index.hbs b/ui/admin/app/components/form/host-catalog/index.hbs
index 5b1666529..49a01af82 100644
--- a/ui/admin/app/components/form/host-catalog/index.hbs
+++ b/ui/admin/app/components/form/host-catalog/index.hbs
@@ -9,4 +9,5 @@
submit=@submit
cancel=@cancel
changeType=@changeType
+ changeCredentialType=@changeCredentialType
}}
\ No newline at end of file
diff --git a/ui/admin/app/components/form/storage-bucket/aws/index.hbs b/ui/admin/app/components/form/storage-bucket/aws/index.hbs
index c38a88a27..4006830f6 100644
--- a/ui/admin/app/components/form/storage-bucket/aws/index.hbs
+++ b/ui/admin/app/components/form/storage-bucket/aws/index.hbs
@@ -424,7 +424,7 @@
{{t 'resources.storage-bucket.form.role_tags.label'}}
- {{t 'resources.storage-bucket.form.role_tags.label'}}
+ {{t 'resources.storage-bucket.form.role_tags.help'}}
{{#if @model.errors.role_tags}}
@@ -436,11 +436,18 @@
{{/if}}
<:field as |F|>
-
+ >
+ <:key as |K|>
+
+
+ <:value as |V|>
+
+
+
{{/if}}
diff --git a/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js b/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js
index 6fed907f7..ad67ebade 100644
--- a/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js
+++ b/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js
@@ -99,4 +99,14 @@ export default class ScopesScopeHostCatalogsIndexController extends Controller {
if (type === 'plugin') type = 'aws';
this.router.replaceWith({ queryParams: { type } });
}
+
+ /**
+ * Updates credential type
+ * @param {model} hostcatalog
+ * @param {string} credentialType
+ */
+ @action
+ changeCredentialType(hostCatalog, credentialType) {
+ hostCatalog.credentialType = credentialType;
+ }
}
diff --git a/ui/admin/app/templates/scopes/scope/host-catalogs/new.hbs b/ui/admin/app/templates/scopes/scope/host-catalogs/new.hbs
index b73a75b92..674fb32cc 100644
--- a/ui/admin/app/templates/scopes/scope/host-catalogs/new.hbs
+++ b/ui/admin/app/templates/scopes/scope/host-catalogs/new.hbs
@@ -29,6 +29,7 @@
@submit={{fn this.hostCatalogs.save @model}}
@cancel={{fn this.hostCatalogs.cancel @model}}
@changeType={{this.hostCatalogs.changeType}}
+ @changeCredentialType={{fn this.hostCatalogs.changeCredentialType @model}}
/>