Secure file sharing with per-tenant access control using a single shared stage and Row Access Policies.
Each file is tagged with a tenant, business unit, and file type for fine-grained classification and filtering.
Managed as a DCM (Database Change Management) project following DataOpsBackbone naming conventions.
All Snowflake object names follow the DataOpsBackbone naming standard:
| Level | Pattern | This Project |
|---|---|---|
| Database | {DOMAIN}_{ENV} |
ECO_DEV |
| Schema | {DOMAIN}_{MATURITY}_v{NNN} |
ECO_RAW_v001 |
| Objects | {DOMAIN}{COMP}_{MATURITY}_{TYPE}_{TEXT} |
ECOS_RAW_TB_STAGE_FILES_MT |
- Domain:
ECO(Ecosystem) - Component:
S(Sharing) - Maturity:
RAW
sharing_any_objects/
├── manifest.yml # DCM project manifest (templating variables)
├── pre_deploy.sql # Database, schema, DCM project creation
├── post_deploy.sql # Secure view, RAP, stored procedure
├── setup.sql # Application Package setup (ACCOUNTADMIN, one-time)
├── post_deployment_grants.sql # Role grants and contacts (run with ACCOUNTADMIN)
├── .github/
│ └── workflows/
│ └── update-local-repo.yml # CI/CD pipeline
├── sources/
│ └── definitions/
│ ├── infrastructure.sql # Stage definition (DEFINE)
│ └── tables.sql # Table definitions (DEFINE)
├── sqlunit/
│ └── tests.sqltest # SQL validation tests
├── app/
│ └── app.py # Streamlit file download portal
├── upload_mt.py # Upload files with tenant registration
├── download_files.py # Download files for the current role
├── connections.toml.example # Snowflake connection template
└── README.md
LOCAL ACCESS (same account) CROSS-ACCOUNT ACCESS (via listing)
============================ =================================
+------------------+ +------------------+
| BANK1_USER | | Consumer Account |
| (BANK1_ROLE) | | (e.g. GK68488) |
+--------+---------+ +--------+---------+
| |
v v
+--------+---------+ +--------+---------+
| BANK1_ROLE | | ACCOUNTADMIN |
+--------+---------+ +--------+---------+
| |
| CURRENT_ROLE() = TENANT_ID | CURRENT_ROLE() IS NULL
| | (Snowflake hides consumer role
| | inside shared secure views)
| |
+--------------------+--------------------+
|
+------------------+-------------------+
| ECOS_RAW_VW_STAGE_FILES_DOWNLOAD_MT |
| (SECURE VIEW) |
| shared via listing/share |
+------------------+-------------------+
|
+------------------+-------------------+
| ECOS_RAW_PL_STAGE_FILES_MT |
| (ROW ACCESS POLICY) |
| |
| 1. CURRENT_ROLE() = 'ACCOUNTADMIN' |
| 2. CURRENT_ROLE() = TENANT_ID |
| 3. CURRENT_ROLE() IS NULL |
| AND CURRENT_ACCOUNT() matches |
| ECOS_RAW_TB_CONSUMER_ROLE_MAPPING|
+------------------+-------------------+
|
+------------------+-------------------+
| ECOS_RAW_TB_STAGE_FILES_MT |
| TENANT_ID | BUSINESS_UNIT | |
| FILE_TYPE | FILE_NAME | FILE_PATH |
+------------------+-------------------+
^
| ECOS_RAW_SP_REGISTER_FILE()
| (called on every upload)
|
+------------------+-------------------+
| @ECOS_RAW_ST_DOC_MT |
| (single shared stage) |
+--------------------------------------+
ECOS_RAW_TB_CONSUMER_ROLE_MAPPING (cross-account access)
+------------------+---------------+-----------+
| CONSUMER_ACCOUNT | CONSUMER_ROLE | TENANT_ID |
+------------------+---------------+-----------+
| GK68488 | BANK1_ROLE | BANK1 |
| GK68488 | BANK2_ROLE | BANK2 |
+------------------+---------------+-----------+
Note: Use account LOCATOR (e.g. GK68488), not org.account format.
CURRENT_ROLE() is NULL in shared views -- only CURRENT_ACCOUNT() is used.
| Object | Type | Managed By | Description |
|---|---|---|---|
ECOS_RAW_ST_DOC_MT |
Internal Stage | DCM DEFINE | Single shared stage for all tenants |
ECOS_RAW_TB_STAGE_FILES_MT |
Table | DCM DEFINE | File metadata with TENANT_ID, BUSINESS_UNIT, FILE_TYPE |
ECOS_RAW_TB_CONSUMER_ROLE_MAPPING |
Table | DCM DEFINE | Maps consumer account locators to tenant IDs |
ECOS_RAW_VW_STAGE_FILES_DOWNLOAD_MT |
Secure View | post_deploy.sql | Per-tenant file list with download URLs |
ECOS_RAW_PL_STAGE_FILES_MT |
Row Access Policy | post_deploy.sql | Local + cross-account tenant isolation |
ECOS_RAW_SP_REGISTER_FILE |
Stored Procedure | post_deploy.sql | Upserts file metadata on upload (MERGE) |
- Snowflake CLI >= 3.16 (
snow --version) - A Snowflake connection configured (
~/.snowflake/connections.toml) - Account:
zs28104.eu-central-1
# 1. Create database, schema, and DCM project
snow sql -f pre_deploy.sql -c <connection> --role ACCOUNTADMIN
# 2. Analyze definitions
snow dcm raw-analyze ECO_DEV.ECO_DCM.SHARING_OBJECTS -c <connection> --target DEV
# 3. Plan changes
snow dcm plan ECO_DEV.ECO_DCM.SHARING_OBJECTS -c <connection> --target DEV --save-output
# 4. Deploy
snow dcm deploy ECO_DEV.ECO_DCM.SHARING_OBJECTS -c <connection> --target DEV --alias "initial"
# 5. Run post-deployment (secure view, RAP, stored procedure)
snow sql -f post_deploy.sql -c <connection> --role CICD
# 6. Apply grants and contacts (requires ACCOUNTADMIN)
snow sql -f post_deployment_grants.sql -c <connection> --role ACCOUNTADMINThe .github/workflows/update-local-repo.yml workflow automates:
- SonarQube SQL linting
- DCM analyze + plan + deploy
- Schema clone for regression testing
- SQLUnit validation tests
- GitHub Release creation
When the secure view is shared via a Snowflake listing:
CURRENT_ROLE()returns NULL inside shared secure viewsCURRENT_ACCOUNT()returns the consumer's account locator (e.g.GK68488)- The RAP falls through to
ECOS_RAW_TB_CONSUMER_ROLE_MAPPING
To onboard a new consumer account:
INSERT INTO ECO_DEV.ECO_RAW_v001.ECOS_RAW_TB_CONSUMER_ROLE_MAPPING
(CONSUMER_ACCOUNT, CONSUMER_ROLE, TENANT_ID)
VALUES ('GK68488', 'BANK1_ROLE', 'BANK1');pip install snowflake-connector-python streamlitCopy connections.toml.example to ~/.snowflake/connections.toml and fill in your PAT tokens:
[bank1]
account = "zs28104.eu-central-1"
user = "BANK1_USER"
role = "BANK1_ROLE"
database = "ECO_DEV"
schema = "ECO_RAW_v001"
warehouse = "MD_TEST_WH"
authenticator = "programmatic_access_token"
token = "<PAT_TOKEN>"Generate PAT tokens in Snowsight under Admin > Security > Programmatic Access Tokens.
python3 upload_mt.py --connection <admin_connection> --tenant-id BANK1 --business-unit Tax --type IRS --files invoice.pdf report.pdfpython3 download_files.py --connection bank1 --list-onlypython3 download_files.py --connection bank1 --output-dir ./downloadsstreamlit run app/app.pyOpen http://localhost:8501 in your browser. Select a role, then filter by Business Unit and File Type to narrow down the file list.
Tests are in sqlunit/tests.sqltest and validate:
- Table existence (
ECOS_RAW_TB_STAGE_FILES_MT,ECOS_RAW_TB_CONSUMER_ROLE_MAPPING) - Stage existence (
ECOS_RAW_ST_DOC_MT) - View existence (
ECOS_RAW_VW_STAGE_FILES_DOWNLOAD_MT) - Column presence (
TENANT_ID,BUSINESS_UNIT,FILE_TYPE,FILE_PATH,REFRESHED_AT,CONSUMER_ACCOUNT) - Column count for each table
| File | Description |
|---|---|
manifest.yml |
DCM project manifest with DEV target and templating variables |
pre_deploy.sql |
Creates database, schema, DCM project |
post_deploy.sql |
Secure view, RAP, stored procedure |
post_deployment_grants.sql |
Role grants, future grants, warehouse access, contacts (ACCOUNTADMIN) |
sources/definitions/infrastructure.sql |
Stage definition (DCM DEFINE) |
sources/definitions/tables.sql |
Table definitions with column comments (DCM DEFINE) |
sqlunit/tests.sqltest |
SQL validation tests |
upload_mt.py |
Upload files with tenant, business unit, and type registration |
download_files.py |
Download files for the current role |
app/app.py |
Streamlit file download portal with role, business unit, and type filters |
connections.toml.example |
Snowflake connection template |