Secure multi-tenancy in MySQL application - mysql

I have a JSP/MySQL web service where users interact with "processes" -- they can upload data, configure, view reports, etc for a given process. They can also create new processes or run reports that compare several processes.
Currently, the process id is specified in the URL (a GET parameter) so any user can interact with any process. I have been asked to add security and multi-tenancy to this service. For simplicity, let's say each tenant has full access to a set of processes, but processes may be accessible by multiple tenants.
My preferred approach:
Add a user table (PK_User_Id, password_hash, name, etc)
Add an access table (FK_User_Id, FK_Process_Id)
An SSL login page that stores the Tenant_Id in the Session
A process-select page that lets you choose a Process_Id that you have access to, and stores that in the Session
Almost every page will create its SQL queries based on the Session's Process_Id
"Cross-process" pages like Create, Select, and Compare will work off of the Session's User_Id instead
My boss thinks that this is not secure "enough" to satisfy an external code audit. He fears that a wayward developer could still write a query that exposes one customer's data to another, or something.
He wants me to also use ANSI SQL's built in ROLES (the app must stay DB agnostic) to create a db role for each user. The role will detail which tables the role has access to, which rows in shared tables, etc. This way, upon login, the Connection will be "safe" and no developer mistake can possibly cause issues.
Is this possible?
Are there such a thing as DB-agnostic "Roles" that work with MySQL?
Can the roles specify that you are allowed to add rows to a table iff the primary key is 'foo'?
Is my system "secure enough" by industry standards?

Here is what I do for MySQL multi-tenant with a single database to ensure data is private:
Create a mysql user for each tenant
Add a tenant_id column to each table
Use a trigger to automatically put the current mysql user into the tenant_id column on INSERT
Create a view for each table that only shows rows where tenant_id = mysql_user (do not include the tenant_id column in the view)
Restrict the tenant mysql user to only have access to these views
Since the application is using the tenant's mysql user there is no chance that they can accidentally get another tenant's data.
I was able to convert a large single-tenant mysql application to multi-tenant in a weekend with minimal changes. I documented the design here: https://opensource.io/it/mysql-multi-tenant/

use PostgreSQL instead, as it supports real schemas, unlike MySQL
if you have to use MySQL, do the following:
make one mysql user per tenant
add an indexed column to each table, tenant VARCHAR(16) NOT NULL
add a trigger to each table that sets tenant to the mysql connection username ON BEFORE INSERT
create a view for each table that sets WHERE tenant = mysql connection username. DO NOT include the tenant column in the select list
grant permission to the tenant user for views, but not for tables
And now the user can only see their own tenant information.

We had a similar discussion on multitenancy security and handling requests on so question. But in short I think storing tenantID in session is a huge security risk. User can go from one tenant to other and tenantID will remain the same, also tenantID should not be send through url.

Related

postgres schemas and roles in mySQL

I have been using PostGresSQL as the database for one of my applications. To support multi-tenancy, I used schemas and roles for each tenant so as to limit access and prevent data leaks in event of an SQL injection. I am maintaining a single connection pool and then doing a SET ROLE after determining the tenant context so he can access only his own schema. This all works well. However, what is the equivalent design in mySQL ? I saw that mySQL does not have "roles" and a schema / database is the same conceptually, how can I achieve something similar in mySQL ? I ask because I am designing another application and am being told to use mySQL instead of PGSQL.
Thanks
Since mysql does not have the concept of roles, you either have to use different mysql database users and databases to achieve the same logical separation of data. Effectively, after determining the context, you have to connect to mysql using a different myswl user account and a different default database. The drawback is that this solution will render connection pooling inert. Fortunately, in MySQL establishing a new connection to the database is quick and does not require too much resources.
Alternatively, you can use a single database and mysql user account and distinguish between you users on application user account level. Obviously, this means that your users' data will not have the same logical separation as you currently have, but you can still use connection pooling.
As a third alternative with limited number of users you can use the same mysql user account and default database to connect to mysql, but store actual user data in separate databases only accessible using separate mysql user accounts. However, in that default database create separate views for each and every table in the user databases. In the create view statement set the definer clause to the mysql user account that can access the given database where the table is stored and set sql security clause to definer. This way you can still use connection pooling, since the connections are made using a common user id to a default database. The client data will be logically separated in the databases. The drawback is that through the views within the default database all data will be accessible and any modification to the underlying data structure must be reflected in the views as well.

Is it possible to make certain data available to certain users on a public mysql database?

Let's say we have a public DNA database running on mysql. Database contains only complete data. In this scenario, some special users want to add experimental data to the database, which may not be complete or they don't want it to be visible to everyone. Instead they want the experimental data to only be visible to users with correct privileges. What approach would you take to achieve this?
Presumably these datasets are large, and performance is important. That means the privilege system should be as coarse as possible.
If I were doing this, I'd create a "public" database, and use the MySQL GRANT command to allow guest users to SELECT on that database.
For example:
CREATE USER 'guest'#'%' IDENTIFIED BY 'changethispassword';
GRANT SELECT ON public.* TO 'guest'#'%';
Then, for the nonpublic datasets, I'd put them into other databases, and be more selective about the users GRANTed privileges. For example, these GRANTs give two different users access to private information and the public information.
CREATE USER 'venter'#'%' IDENTIFIED BY 'changethispassword';
GRANT SELECT ON public.* TO 'venter'#'%';
GRANT SELECT ON celera.* TO 'venter'#'%';
CREATE USER 'collins'#'%' IDENTIFIED BY 'changethispassword';
GRANT SELECT ON public.* TO 'collins'#'%';
GRANT SELECT ON hgp.* TO 'collins'#'%';
A user who has SELECT privileges on, let us say, the public database and the celera database, can issue queries like this allowing seamless (if not optimally performing) merging of private and public data.
SELECT whatever
FROM public.AGCT
UNION ALL
SELECT whatever
FROM celera.AGCT
Of course, it has to make scientific sense to take the union of these datasets. That may or may not be the case.
Don't be alarmed at the idea of creating multiple databases. They really are nothing more complex than directories in a computer file system. A single server can deliver dozens of them without any problems.
MySQL is definitely up to this kinds of security. Hosting providers run multi-tenant servers routinely.
I would consider MariaDB (a MySQL-compatible database written by MySQL's founder) over MySQL, as it supports roles.
Neither of them support Row Security like Oracle does, but you can mimic it by adding an "owner" column with the name of the role that can select/update the row.
Add a WITH CHECK OPTION view that checks that the current_user is in the role specified in that column.
Add a trigger to set owner value properly.
update: If you can't alter the table but can add new ones, add a new one w same key as original, and add owner column, and join the tables in your view.
See
http://www.sqlmaestro.com/resources/all/row_level_security_mysql/

How many db-users should I create?

guys!
I need to ask you a question... I'm mew in programming business and if this question seems silly, please indulge me.
I have a little site where people have to register in order to post something. So I register every user in the database. I log into the db with:
$pdo = new PDO("mysql:host=localhost;dbname="MY-DB", "My-USER", "My-password");
The question is: when I log into the databse to do whatever operation I need (select, update, delete, insert) how many users ("MY-USER") should I create to login to the db ? Should I create one db-user for every users that registers on my site, or one one single user is enough to do the operations ?
Thanks.
In normal situations you should have additionally to a root user, which can create new databases and add new users, you should create one user per application which is accessing the database, so that a bug in one application wouldn't affect another.
In your case this means to have 1 additional user for your web application which has all permissions on one mysql-database and can create the necessary tables as well as read / modify data in those.
You should not use mysql as your user manager, instead create a table with the users, their (hashed) passwords, ... and manage them in your application.

different logins access different data from tables

I am using MVC 3, and mssql 2008 r2 and,
I was wondering if there is an automated mechanism that associates different logins to different data from database tables.
For example I want to create a calendar. But I want each user to view only his own entries. So I have a table Appointment with time and place. But I do not want to include an association from LDAP because I will need to do that in a number of places.
Adam
If you are not planning to do this in the application logic, then the only way I can currently think of to do this is through the use of stored procedures or table valued functions.
What you would do is create what are often referred to as CRUD (Create Read Update Delete) stored procedures for the tables. Obviously if you are only reading from the table(s), then only a read sproc is needed.
Within the stored procedure, you can put in logic to filter the results based on the user's login.
You can assign the user to a role in the database and give that role execute privilege on the stored procedure. Or if you are using a table value function, you would give the role SELECT privilege on the function. You would not give the user any privileges to view the table itself.
Ex.
CREATE ROLE CalendarReader AUTHORIZATION dbo;
GO
CREATE PROCEDURE Calendar_Get
AS
SET NOCOUNT ON;
SELECT
EventDate
,EventText
FROM
Calendar
WHERE
UserLogin = suser_sname()
;
GO
GRANT EXECUTE ON Calendar_Get TO CalendarReader;
GO
There is nothing else for it but to have your database be structured in such a way as to know which data belongs to each user. So your CALENDAR table is going to need a user_id column on it. If you are using any ASP.NET web-based application framework, including MVC, you have access to the authentication provider that your application uses.
For an MVC application, your controller needs to pass the user ID (i.e. HttpContext.Current.User.Identity.Name) to the data layer, which needs to use this value in the where clause.
Trying to do this implicitly will just get you into trouble. For example, if you need to have an administrator with access to multiple users' calendars, you can't use an implicit filter. You need to have your controller tell your data layer what it wants.

Is a simple MySql Database possible?

Steps:
I want a registered user to be able to insert values into a table.
Those values would only be able to be seen or edited by the user. (a few rows)
I have a registration/login page and insert form page complete and they can add do their respective jobs.
Here's the problem and i realize it probably a super simple answer:
How do I link the registration/login username to the values that I'm entering so that only that username has access to it?
Thanks,
Michael
You can create a MySQL user for each registered user and protect their data at the DB level. That's usually overkill for a web application.
What you probably want here is enforcing data owner at the data access layer. Associate the data to the user and restrict any data queries or updates to that user, i.e. any insert, update, select SQL statements would include the user id as a parameter.