Azure Synapse Analytics Security: Access Control
Tags: azure, dataengineering, sqlserver, database
Published: 2024-07-14 12:40:32

## Introduction
The assets of a bank are only accessible to some high-ranking officials, and even they don't have access to individual user lockers. These privacy features help build trust among customers. The same goes with in our IT world. Every user wants their sensitive data to be accessible only to themselves, not even available to those with higher privileges within the company. So, as you move data to the cloud, securing the data assets is critical to building trust with your customers and partners.

To enable these kinds of preventions, Azure Synapse supports a wide range of advanced access control features to control who can access what data. These features are:

- Object-level security
- Row-level security
- Column-level security
- Dynamic data masking
- Synapse role-based access control

In this blog we will explore these features.

--- 

## Object-level security
In Azure Synapse, whenever we create tables, views, stored procedures, and functions, they are created as objects. In a dedicated SQL pool these objects can be secured by granting specific permissions to database-level users or groups.

For example, you can give `SELECT` permissions to user accounts or [database roles](https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/database-level-roles?view=sql-server-ver15&preserve-view=true) to give access to specific objects.

To assign permission:
`GRANT SELECT ON [schema_name].[table_name] TO [user_or_group];`

To revoke permission:
`REVOKE SELECT ON [schema_name].[table_name] FROM [user_or_group];`

Additionally, when you assign a user to [Synapse Administrator RBAC role](https://learn.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-synapse-rbac-roles), they automatically gain full access to all dedicated SQL pools within that workspace. It allows them to perform any action (including managing permissions) across all databases.

In Addition, when a user assigned to the Storage Blob Data Contributor role (have READ, WRITE, and EXECUTE permissions) of data lakes and the data lakes is connected to the workspace like Synapse or Databricks, then these permissions automatically applied to the Spark-created tables. This is known as _**Microsoft Entra pass-through**_.

See, when Storage Blob Data Contributor role assigned to me:
![Role Assign](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5iudywcmrgt76ud0pmni.jpeg)


Then I am able to query my Spark-created table.

![Query Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1cwg626sdrtrbvbie9ol.jpeg)


But, when I removed that role from myself. Then it gave me an error!

![Unauthorised query access](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/893uoedvodatywlc0vdi.jpeg)
 
---

## [Row-level security](https://learn.microsoft.com/en-us/sql/relational-databases/security/row-level-security?view=azure-sqldw-latest)

![RLS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kycwjommlkcy0dhw5ypk.png)


RLS is a mechanism to restrict row level access (read, write, ...), based on the user's context data. A typical use cases is like, A common database tables used by multiple tenants to store the data, and in such case, we want each tenant to restrict access to their own data only.

It enables this fine-grained access control without having to redesign your data warehouse. It also eliminates the need to use `Views` to filter out rows for access control management. 

> **NOTE**: The access restriction logic is located in the database tier and the database system applies the access restrictions every time when the data is access from any tier. This makes the security system more reliable and robust by reducing the surface area of your security system.

<u>_How to implement RLS?_</u>

RLS can be implemented by using [SECURITY POLICY](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-security-policy-transact-sql?view=azure-sqldw-latest). RLS is a form of predicate-based access control that works by automatically applying a Security Predicate to all queries on a table. Security Predicate binds the predicate function to the table. Predicate Function is basically a user defined function which determines a user executing the query will have access to the row or not.

![SECURITY POLICY](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aohf9ciaoznjg2or7cfe.png)


**There are two types of security predicates:**

- **_Filter predicates:_** It silently filters out rows that users shouldn't see during SELECT, UPDATE, and DELETE operations. This is used when you want to hide data without disrupting the user experience. For example, in an employee database, filter predicate is used to ensure salespeople can only see their own customer records. They wouldn't even know about records belonging to other salespeople.

- **_Block predicates:_** It explicitly blocking write operations (INSERT, UPDATE, DELETE) that violate pre-defined rules. If a user tries to perform an action that breaks the rules, the operation fails with an error message. This is used where you want to prevent unauthorized modifications.

_<u>Implementing Filter Predicates</u>_
**Step 1:** 
Creating dummy users and tables, and then grant read access to these objects.

```
CREATE SCHEMA Sales
GO

CREATE TABLE Sales.Region
    (  
    id int,  
    SalesRepName nvarchar(50),  
    Region nvarchar(50),  
    CustomerName nvarchar(50)  
    );

-- Inserting data 
INSERT INTO Sales.Region VALUES (1, 'Mann', 'Central Canada', 'C1');
INSERT INTO Sales.Region VALUES (2, 'Anna', 'East Canada', 'E1');
INSERT INTO Sales.Region VALUES (3, 'Anna', 'East Canada', 'E2');
INSERT INTO Sales.Region VALUES (4, 'Mann', 'Central Canada', 'C2');
INSERT INTO Sales.Region VALUES (6, 'Anna', 'East Canada', 'E3');

-- Creating Users
CREATE USER SalesManager WITHOUT LOGIN;  
CREATE USER Mann WITHOUT LOGIN;  
CREATE USER Anna WITHOUT LOGIN;

-- Granting Read Access to the Users
GRANT SELECT ON Sales.Region TO SalesManager;  
GRANT SELECT ON Sales.Region TO Mann;  
GRANT SELECT ON Sales.Region TO Anna; 
```

**Step 2:**
Create Security Filter Predicate Function.

```
--Creating Schema for Security Predicate Function
CREATE SCHEMA spf; 

CREATE FUNCTION spf.securitypredicatefunc(@SaleRepName AS NVARCHAR(50))
    RETURNS TABLE
WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS securitypredicate_result
WHERE @SaleRepName = USER_NAME() OR USER_NAME() = 'SalesManager';
```
The function returns a table with a single value that is 1, when it satisfies the `WHERE` condition. And `SCHEMABINDING` ensures that the underlying objects (tables, views, etc.) referenced by the function cannot be modified (dropped or altered) while the function exists.

**Step 3:**
Create Security Policy that Filter Predicate Security and binds the predicate function to the table.

```
CREATE SECURITY POLICY MySalesFilterPolicy  
ADD FILTER PREDICATE spf.securitypredicatefunc(SalesRepName)
ON Sales.Region
WITH (STATE = ON);  
```
 
**Step 4:**
Test your RLS.

```
EXECUTE AS USER = 'Mann';  
SELECT * FROM Sales.Region
ORDER BY id;
REVERT;  
```
When a user (e.g., 'Mann') executes a query on the table, SQL Server automatically invokes the security predicate function for each row in the table. Internally the function is called by SQL Server as part of the query execution plan. So, the permissions required to execute the functions are inherently handled by the SQL Server engine. So, there is no need to explicitly give the permission to functions.

![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t61fs7whfon1hnrgxzlh.png)

**Step 5:**
You can disable RLS by Altering the Security Policy.
```
ALTER SECURITY POLICY MySalesFilterPolicy  
WITH (STATE = OFF);
```

---

## Column-level security

![CLS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xba3lsu4gyn0ol30r96j.png)

It is similar to RLS, but as its name suggests, it applies at the column level. For example, in financial services, only account managers have access to customer social security numbers (SSN), phone numbers, and other personally identifiable information (PII).

Additionally, the method of implementing CLS differs. It is implemented by granting Object level Security.

_<u>Implementing CLS</u>_
**Step 1:**
Creating dummy user and table.

```
CREATE USER TestUser WITHOUT LOGIN;

CREATE TABLE Membership (
    MemberID int IDENTITY,
    FirstName varchar(100) NULL,
    SSN char(9) NOT NULL,
    LastName varchar(100) NOT NULL,
    Phone varchar(12) NULL,
    Email varchar(100) NULL
);
```
**Step 2:**
Grant the User to access columns except sensitive columns.

```
GRANT SELECT ON Membership (
    MemberID, 
    FirstName, 
    LastName, 
    Phone, 
    Email
) TO TestUser;
```

**Step 3:**
Now if the user try to access whole columns, it will give error.
```
EXECUTE AS USER = 'TestUser';
SELECT * FROM Membership;
```
![Error output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d71ewgznmqihj8yko44h.png)

--- 

## [Dynamic data masking](https://learn.microsoft.com/en-us/sql/relational-databases/security/dynamic-data-masking?view=sql-server-ver16#best-practices-and-common-use-cases)

![Dynamic Data Masking](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/onuq98kt4u4gupwtc2hl.png)
It is the process of limiting the exposure of sensitive data, to the user who should not have access to viewing it. For example, Customer service agents who need to access customer records but should not see full credit card numbers, which can be masked. 

**You may ask, why can't we use CLS, or why we don't completely restrict the access?**

Because of these reasons: -

- A CLS will completely restrict the access of reading and altering columns. But when a masking is applied on a column, it doesn't prevent updates to that column. So, if users receive masked data while querying the masked column, the same users can update the data if they have write permissions.
- In masking you can use `SELECT INTO` or `INSERT INTO` to copy data from a masked column into another table that will store as masked data(assuming it's exported by a user without UNMASK privileges). But in CLS you can't do anything, if you don't have access to restricted column.

> **NOTE**:

- Administrative users and roles (such as sysadmin or db_owner) can always view unmasked data via the CONTROL permission, which includes both the `ALTER ANY MASK` and `UNMASK` permission. 

-  You can grant, or revoke UNMASK permission at the database-level, schema-level, table-level or at the column-level to a user, database role, Microsoft Entra identity or Microsoft Entra group.

_<u>Implementing DDM</u>_
**Step 1:**
Creating dummy user.
`CREATE USER MaskingTestUser WITHOUT LOGIN;`

**Step 2:**
Create a table and apply the masking on required columns.

```
CREATE SCHEMA Data;
GO

CREATE TABLE Data.Membership (
    FirstName VARCHAR(100) MASKED WITH (FUNCTION = 'partial(1, "xxxxx", 1)') NULL,
    LastName VARCHAR(100) NOT NULL,
    Phone VARCHAR(12) MASKED WITH (FUNCTION = 'default()') NULL,
    Email VARCHAR(100) MASKED WITH (FUNCTION = 'email()') NOT NULL,
    DiscountCode SMALLINT MASKED WITH (FUNCTION = 'random(1, 100)') NULL
);

-- inserting sample data
INSERT INTO Data.Membership
VALUES
('Kapil', 'Dev', '555.123.4567', 'kapil@team.com', 10);
```
Here, you see I have applied both default and custom masking functions.

**Step 3:**
Granting the `SELECT` permission on the schema where the table resides. Users view masked data.
`GRANT SELECT ON SCHEMA::Data TO MaskingTestUser;`

![Masked Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qyax1rmytc53gmclnkeu.png)

**Step 4:**
Granting the `UNMASK` permission allows Users to see unmasked data.
`GRANT UNMASK TO MaskingTestUser;`
![Unmasked Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wiagnr4x3bxy7oehwhbt.png)

**Step 5:**
Use the `ALTER TABLE` statement to add a mask to an existing column in the table, or to edit the mask on that column.

```
ALTER TABLE Data.Membership
ALTER COLUMN LastName ADD MASKED WITH (FUNCTION = 'partial(2,"xxxx",0)');

ALTER TABLE Data.Membership
ALTER COLUMN LastName VARCHAR(100) MASKED WITH (FUNCTION = 'default()');
```

---

## [Synapse role-based access control](https://learn.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-understand-what-role-you-need)
Basically, it leverages the built-in roles to assign permissions to users, groups, or other security principals to manage who can:

- Publish code artifacts and list or access published code artifacts.
- Execute code on Apache Spark pools and integration runtimes.
- Access linked (data) services that are protected by credentials.
- Monitor or cancel job executions, review job output and execution logs.