From 0698ce25ef80b27b6ad0857893c24e8db8db2eef Mon Sep 17 00:00:00 2001 From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> Date: Thu, 6 May 2021 13:22:40 -0500 Subject: [PATCH] UI/database mysql (#11532) * Add MySQL DB Support * Add other versions of MySQL to database options * Save incoming root_credentials_rotate_statements as root_rotation_statements for display * Handle errors correctly on database connection form for edit * Add tests for mysql database * Add UI feature changelog --- changelog/11532 | MySQL Database UI.txt | 3 + ui/app/components/database-connection.js | 12 +- .../components/database-role-setting-form.js | 8 ++ ui/app/models/database/connection.js | 80 ++++++++++++++ ui/app/serializers/database/connection.js | 6 +- .../secrets/backend/database/secret-test.js | 104 +++++++++++++++++- .../database-role-setting-form-test.js | 20 ++++ 7 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 changelog/11532 | MySQL Database UI.txt diff --git a/changelog/11532 | MySQL Database UI.txt b/changelog/11532 | MySQL Database UI.txt new file mode 100644 index 0000000000000..59760fa8709bd --- /dev/null +++ b/changelog/11532 | MySQL Database UI.txt @@ -0,0 +1,3 @@ +```release-note:feature +**MySQL Database UI**: The UI now supports adding and editing MySQL connections in the database secret engine +``` diff --git a/ui/app/components/database-connection.js b/ui/app/components/database-connection.js index efb13943c23d3..b6b3c0da2eb03 100644 --- a/ui/app/components/database-connection.js +++ b/ui/app/components/database-connection.js @@ -102,9 +102,15 @@ export default class DatabaseConnectionEdit extends Component { evt.preventDefault(); let secret = this.args.model; let secretId = secret.name; - secret.save().then(() => { - this.transitionToRoute(SHOW_ROUTE, secretId); - }); + secret + .save() + .then(() => { + this.transitionToRoute(SHOW_ROUTE, secretId); + }) + .catch(e => { + const errorMessage = getErrorMessage(e.errors); + this.flashMessages.danger(errorMessage); + }); } @action diff --git a/ui/app/components/database-role-setting-form.js b/ui/app/components/database-role-setting-form.js index d90009feb592b..cdea6b57c7d13 100644 --- a/ui/app/components/database-role-setting-form.js +++ b/ui/app/components/database-role-setting-form.js @@ -27,11 +27,19 @@ const STATEMENT_FIELDS = { default: ['rotation_statements'], 'mongodb-database-plugin': [], 'mssql-database-plugin': [], + 'mysql-database-plugin': [], + 'mysql-aurora-database-plugin': [], + 'mysql-rds-database-plugin': [], + 'mysql-legacy-database-plugin': [], }, dynamic: { default: ['creation_statements', 'revocation_statements', 'rollback_statements', 'renew_statements'], 'mongodb-database-plugin': ['creation_statement', 'revocation_statement'], 'mssql-database-plugin': ['creation_statements', 'revocation_statements'], + 'mysql-database-plugin': ['creation_statements', 'revocation_statements'], + 'mysql-aurora-database-plugin': ['creation_statements', 'revocation_statements'], + 'mysql-rds-database-plugin': ['creation_statements', 'revocation_statements'], + 'mysql-legacy-database-plugin': ['creation_statements', 'revocation_statements'], }, }; export default class DatabaseRoleSettingForm extends Component { diff --git a/ui/app/models/database/connection.js b/ui/app/models/database/connection.js index 641227abf5672..aeda7b28d7b45 100644 --- a/ui/app/models/database/connection.js +++ b/ui/app/models/database/connection.js @@ -41,6 +41,86 @@ const AVAILABLE_PLUGIN_TYPES = [ { attr: 'root_rotation_statements', group: 'statements' }, ], }, + { + value: 'mysql-database-plugin', + displayName: 'MySQL/MariaDB', + fields: [ + { attr: 'plugin_name' }, + { attr: 'name' }, + { attr: 'verify_connection' }, + { attr: 'password_policy' }, + { attr: 'connection_url', group: 'pluginConfig' }, + { attr: 'username', group: 'pluginConfig', show: false }, + { attr: 'password', group: 'pluginConfig', show: false }, + { attr: 'max_open_connections', group: 'pluginConfig' }, + { attr: 'max_idle_connections', group: 'pluginConfig' }, + { attr: 'max_connection_lifetime', group: 'pluginConfig' }, + { attr: 'username_template', group: 'pluginConfig' }, + { attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'root_rotation_statements', group: 'statements' }, + ], + }, + { + value: 'mysql-aurora-database-plugin', + displayName: 'MySQL (Aurora)', + fields: [ + { attr: 'plugin_name' }, + { attr: 'name' }, + { attr: 'verify_connection' }, + { attr: 'password_policy' }, + { attr: 'connection_url', group: 'pluginConfig' }, + { attr: 'username', group: 'pluginConfig', show: false }, + { attr: 'password', group: 'pluginConfig', show: false }, + { attr: 'max_open_connections', group: 'pluginConfig' }, + { attr: 'max_idle_connections', group: 'pluginConfig' }, + { attr: 'max_connection_lifetime', group: 'pluginConfig' }, + { attr: 'username_template', group: 'pluginConfig' }, + { attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'root_rotation_statements', group: 'statements' }, + ], + }, + { + value: 'mysql-rds-database-plugin', + displayName: 'MySQL (RDS)', + fields: [ + { attr: 'plugin_name' }, + { attr: 'name' }, + { attr: 'verify_connection' }, + { attr: 'password_policy' }, + { attr: 'connection_url', group: 'pluginConfig' }, + { attr: 'username', group: 'pluginConfig', show: false }, + { attr: 'password', group: 'pluginConfig', show: false }, + { attr: 'max_open_connections', group: 'pluginConfig' }, + { attr: 'max_idle_connections', group: 'pluginConfig' }, + { attr: 'max_connection_lifetime', group: 'pluginConfig' }, + { attr: 'username_template', group: 'pluginConfig' }, + { attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'root_rotation_statements', group: 'statements' }, + ], + }, + { + value: 'mysql-legacy-database-plugin', + displayName: 'MySQL (Legacy)', + fields: [ + { attr: 'plugin_name' }, + { attr: 'name' }, + { attr: 'verify_connection' }, + { attr: 'password_policy' }, + { attr: 'connection_url', group: 'pluginConfig' }, + { attr: 'username', group: 'pluginConfig', show: false }, + { attr: 'password', group: 'pluginConfig', show: false }, + { attr: 'max_open_connections', group: 'pluginConfig' }, + { attr: 'max_idle_connections', group: 'pluginConfig' }, + { attr: 'max_connection_lifetime', group: 'pluginConfig' }, + { attr: 'username_template', group: 'pluginConfig' }, + { attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' }, + { attr: 'root_rotation_statements', group: 'statements' }, + ], + }, ]; /** diff --git a/ui/app/serializers/database/connection.js b/ui/app/serializers/database/connection.js index 7112920b36981..20982af03568a 100644 --- a/ui/app/serializers/database/connection.js +++ b/ui/app/serializers/database/connection.js @@ -19,13 +19,17 @@ export default RESTSerializer.extend({ return connections; } // Query single record response: - return { + let response = { id: payload.id, name: payload.id, backend: payload.backend, ...payload.data, ...payload.data.connection_details, }; + if (payload.data.root_credentials_rotate_statements) { + response.root_rotation_statements = payload.data.root_credentials_rotate_statements; + } + return response; }, normalizeResponse(store, primaryModelClass, payload, id, requestType) { diff --git a/ui/tests/acceptance/secrets/backend/database/secret-test.js b/ui/tests/acceptance/secrets/backend/database/secret-test.js index 10fce034c4a36..2123bb8022f9d 100644 --- a/ui/tests/acceptance/secrets/backend/database/secret-test.js +++ b/ui/tests/acceptance/secrets/backend/database/secret-test.js @@ -47,10 +47,13 @@ const connectionTests = [ plugin: 'mongodb-database-plugin', url: `mongodb://127.0.0.1:4321/test`, requiredFields: async (assert, name) => { - assert.dom('[data-test-input="username').exists(`Username field exists for ${name}`); - assert.dom('[data-test-input="password').exists(`Password field exists for ${name}`); - assert.dom('[data-test-input="write_concern').exists(`Write concern field exists for ${name}`); + assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); + assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); + assert.dom('[data-test-input="write_concern"]').exists(`Write concern field exists for ${name}`); assert.dom('[data-test-toggle-group="TLS options"]').exists('TLS options toggle exists'); + assert + .dom('[data-test-input="root_rotation_statements"]') + .exists(`Root rotation statements exists for ${name}`); }, }, { @@ -58,8 +61,73 @@ const connectionTests = [ plugin: 'mssql-database-plugin', url: `mssql://127.0.0.1:4321/test`, requiredFields: async (assert, name) => { - assert.dom('[data-test-input="username').exists(`Username field exists for ${name}`); - assert.dom('[data-test-input="password').exists(`Password field exists for ${name}`); + assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); + assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); + assert + .dom('[data-test-input="max_open_connections"]') + .exists(`Max open connections exists for ${name}`); + assert + .dom('[data-test-input="max_idle_connections"]') + .exists(`Max idle connections exists for ${name}`); + assert + .dom('[data-test-input="max_connection_lifetime"]') + .exists(`Max connection lifetime exists for ${name}`); + assert + .dom('[data-test-input="root_rotation_statements"]') + .exists(`Root rotation statements exists for ${name}`); + }, + }, + { + name: 'mysql-connection', + plugin: 'mysql-database-plugin', + url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, + requiredFields: async (assert, name) => { + assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); + assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); + assert + .dom('[data-test-input="max_open_connections"]') + .exists(`Max open connections exists for ${name}`); + assert + .dom('[data-test-input="max_idle_connections"]') + .exists(`Max idle connections exists for ${name}`); + assert + .dom('[data-test-input="max_connection_lifetime"]') + .exists(`Max connection lifetime exists for ${name}`); + assert.dom('[data-test-toggle-group="TLS options"]').exists('TLS options toggle exists'); + assert + .dom('[data-test-input="root_rotation_statements"]') + .exists(`Root rotation statements exists for ${name}`); + }, + }, + { + name: 'mysql-aurora-connection', + plugin: 'mysql-aurora-database-plugin', + url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, + requiredFields: async (assert, name) => { + assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); + assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); + assert + .dom('[data-test-input="max_open_connections"]') + .exists(`Max open connections exists for ${name}`); + assert + .dom('[data-test-input="max_idle_connections"]') + .exists(`Max idle connections exists for ${name}`); + assert + .dom('[data-test-input="max_connection_lifetime"]') + .exists(`Max connection lifetime exists for ${name}`); + assert.dom('[data-test-toggle-group="TLS options"]').exists('TLS options toggle exists'); + assert + .dom('[data-test-input="root_rotation_statements"]') + .exists(`Root rotation statements exists for ${name}`); + }, + }, + { + name: 'mysql-rds-connection', + plugin: 'mysql-rds-database-plugin', + url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, + requiredFields: async (assert, name) => { + assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); + assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert .dom('[data-test-input="max_open_connections"]') .exists(`Max open connections exists for ${name}`); @@ -69,6 +137,32 @@ const connectionTests = [ assert .dom('[data-test-input="max_connection_lifetime"]') .exists(`Max connection lifetime exists for ${name}`); + assert.dom('[data-test-toggle-group="TLS options"]').exists('TLS options toggle exists'); + assert + .dom('[data-test-input="root_rotation_statements"]') + .exists(`Root rotation statements exists for ${name}`); + }, + }, + { + name: 'mysql-legacy-connection', + plugin: 'mysql-legacy-database-plugin', + url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, + requiredFields: async (assert, name) => { + assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); + assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); + assert + .dom('[data-test-input="max_open_connections"]') + .exists(`Max open connections exists for ${name}`); + assert + .dom('[data-test-input="max_idle_connections"]') + .exists(`Max idle connections exists for ${name}`); + assert + .dom('[data-test-input="max_connection_lifetime"]') + .exists(`Max connection lifetime exists for ${name}`); + assert.dom('[data-test-toggle-group="TLS options"]').exists('TLS options toggle exists'); + assert + .dom('[data-test-input="root_rotation_statements"]') + .exists(`Root rotation statements exists for ${name}`); }, }, ]; diff --git a/ui/tests/integration/components/database-role-setting-form-test.js b/ui/tests/integration/components/database-role-setting-form-test.js index 57154abf3aabf..c734119516311 100644 --- a/ui/tests/integration/components/database-role-setting-form-test.js +++ b/ui/tests/integration/components/database-role-setting-form-test.js @@ -30,6 +30,26 @@ const testCases = [ staticRoleFields: ['username', 'rotation_period'], dynamicRoleFields: ['creation_statements', 'revocation_statements', 'ttl', 'max_ttl'], }, + { + pluginType: 'mysql-database-plugin', + staticRoleFields: ['username', 'rotation_period'], + dynamicRoleFields: ['creation_statements', 'revocation_statements', 'ttl', 'max_ttl'], + }, + { + pluginType: 'mysql-aurora-database-plugin', + staticRoleFields: ['username', 'rotation_period'], + dynamicRoleFields: ['creation_statements', 'revocation_statements', 'ttl', 'max_ttl'], + }, + { + pluginType: 'mysql-rds-database-plugin', + staticRoleFields: ['username', 'rotation_period'], + dynamicRoleFields: ['creation_statements', 'revocation_statements', 'ttl', 'max_ttl'], + }, + { + pluginType: 'mysql-legacy-database-plugin', + staticRoleFields: ['username', 'rotation_period'], + dynamicRoleFields: ['creation_statements', 'revocation_statements', 'ttl', 'max_ttl'], + }, ]; // used to calculate checks that fields do NOT show up