Skip to content

Commit

Permalink
Fix UI when editing database roles (#24660)
Browse files Browse the repository at this point in the history
* Fix UI when editing database roles

When using a database role the UI will try to update the database connection
associated to the role. This is to make sure that the role is allowed to
use this connection:

    async _updateAllowedRoles(store, { role, backend, db, type = 'add' }) {
      const connection = await store.queryRecord('database/connection', { backend, id: db });
      const roles = [...connection.allowed_roles];
      const allowedRoles = type === 'add' ? addToArray([roles, role]) : removeFromArray([roles, role]);
      connection.allowed_roles = allowedRoles;
      return connection.save();
    },

    async createRecord(store, type, snapshot) {
      const serializer = store.serializerFor(type.modelName);
      const data = serializer.serialize(snapshot);
      const roleType = snapshot.attr('type');
      const backend = snapshot.attr('backend');
      const id = snapshot.attr('name');
      const db = snapshot.attr('database');
      try {
        await this._updateAllowedRoles(store, {
          role: id,
          backend,
          db: db[0],
        });
      } catch (e) {
        throw new Error('Could not update allowed roles for selected database. Check Vault logs for details');
      }

      return this.ajax(this.urlFor(backend, id, roleType), 'POST', { data }).then(() => {
        // ember data doesn't like 204s if it's not a DELETE
        return {
          data: assign({}, data, { id }),
        };
      });
    },

This is intended to help the administrator as the role will only work if
it is allowed by the database connection.

This is however an issue if the person doing the update does not have
the permission to update the connection: they will not be able to use
the UI to update the role even though they have the appropriate permissions
to do so (using the CLI or the API will work for example).

This is often the case when the database connections are created by a
centralized system but a human operator needs to create the roles.

You can try this with the following test case:

    $ cat main.tf
    resource "vault_auth_backend" "userpass" {
      type = "userpass"
    }

    resource "vault_generic_endpoint" "alice" {
      depends_on           = [vault_auth_backend.userpass]
      path                 = "auth/userpass/users/alice"
      ignore_absent_fields = true

      data_json = jsonencode({
        "policies" : ["root"],
        "password" : "alice"
      })
    }

    data "vault_policy_document" "db_admin" {
      rule {
        path         = "database/roles/*"
        capabilities = ["create", "read", "update", "delete", "list"]
      }
    }

    resource "vault_policy" "db_admin" {
      name   = "db-admin"
      policy = data.vault_policy_document.db_admin.hcl
    }

    resource "vault_generic_endpoint" "bob" {
      depends_on           = [vault_auth_backend.userpass]
      path                 = "auth/userpass/users/bob"
      ignore_absent_fields = true

      data_json = jsonencode({
        "policies" : [vault_policy.db_admin.name],
        "password" : "bob"
      })
    }

    resource "vault_mount" "db" {
      path = "database"
      type = "database"
    }

    resource "vault_database_secret_backend_connection" "postgres" {
      backend           = vault_mount.db.path
      name              = "postgres"
      allowed_roles     = ["*"]
      verify_connection = false

      postgresql {
        connection_url = "postgres://username:password@localhost/database"
      }
    }
    $ terraform apply --auto-approve

then using bob to create a role associated to the `postgres` connection.

This patch changes the way the UI does the update: it still tries to
update the database connection but if it fails to do so because it does not
have the permission it just silently skip this part and updates the role.

This also update the error message returned to the user in case of issues
to include the actual errors.

* Add changelog

* Also ignore error when deleting a role

* Address code review comments

---------

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
  • Loading branch information
remilapeyre and hashishaw authored Jan 5, 2024
1 parent 0e23ae9 commit 3aee6ec
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 10 deletions.
3 changes: 3 additions & 0 deletions changelog/24660.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: The UI can now be used to create or update database roles by operator without permission on the database connection.
```
28 changes: 21 additions & 7 deletions ui/app/adapters/database/role.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export default ApplicationAdapter.extend({
db: db[0],
});
} catch (e) {
throw new Error('Could not update allowed roles for selected database. Check Vault logs for details');
this.checkError(e);
}

return this.ajax(this.urlFor(backend, id, roleType), 'POST', { data }).then(() => {
Expand All @@ -180,12 +180,16 @@ export default ApplicationAdapter.extend({
const backend = snapshot.attr('backend');
const id = snapshot.attr('name');
const db = snapshot.attr('database');
await this._updateAllowedRoles(store, {
role: id,
backend,
db: db[0],
type: 'remove',
});
try {
await this._updateAllowedRoles(store, {
role: id,
backend,
db: db[0],
type: 'remove',
});
} catch (e) {
this.checkError(e);
}

return this.ajax(this.urlFor(backend, id, roleType), 'DELETE');
},
Expand All @@ -199,4 +203,14 @@ export default ApplicationAdapter.extend({

return this.ajax(this.urlFor(backend, id, roleType), 'POST', { data }).then(() => data);
},

checkError(e) {
if (e.httpStatus === 403) {
// The user does not have the permission to update the connection. This
// can happen if their permissions are limited to the role. In that case
// we ignore the error and continue updating the role.
return;
}
throw new Error(`Could not update allowed roles for selected database: ${e.errors.join(', ')}`);
},
});
3 changes: 0 additions & 3 deletions ui/app/components/database-role-edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export default class DatabaseRoleEdit extends Component {

get warningMessages() {
const warnings = {};
if (this.args.model.canUpdateDb === false) {
warnings.database = `You don’t have permissions to update this database connection, so this role cannot be created.`;
}
if (
(this.args.model.type === 'dynamic' && this.args.model.canCreateDynamic === false) ||
(this.args.model.type === 'static' && this.args.model.canCreateStatic === false)
Expand Down

0 comments on commit 3aee6ec

Please sign in to comment.