Data Engineering · lesson 17 of 23
Beginner
8- 1 Data Engineering Fundamentals
- 2 File Formats: CSV, JSON, Parquet
- 3 Idempotency and Safe Retries
- 4 Batch Pipelines and Time Windows
- 5 Partitioning and Physical Layout
- 6 Batch vs. Streaming: The Real Trade-off
- 7 Data Lake vs. Data Warehouse vs. Lakehouse
- 8 Data Modeling Basics: Star Schemas and Denormalization
Intermediate
4Advanced
11- 13 Streaming: Exactly-Once Semantics
- 14 Schema Evolution and Data Contracts
- 15 Streaming Architecture: Lambda and Kappa
- 16 Data Lineage: Tracing a Column Back to Its Source
- 17 Data Governance: Catalogs, Classification, and Access Policy
- 18 Data Security and PII: Hashing, Masking, and Tokenization
- 19 Pipeline Observability: Tracing, Anomaly Detection, and Alerting
- 20 Cost Optimization: Measuring What Partition and Column Pruning Actually Save
- 21 CI/CD for Data: Gating Promotion with Real Tests
- 22 Disaster Recovery: RPO, RTO, and What a Backup Actually Loses
- 23 Data Platform Architecture: Wiring the Pieces Together
Also part of Data Engineering
9- + Your First Airflow DAG from Airflow
- + Kafka Topics, Partitions, and Offsets from Kafka
- + Spark Architecture: Driver, Executors, and Cluster Managers from Apache Spark
- + Models, ref, and the DAG from dbt
- + Delta Lake: The Transaction Log from Databricks
- + The Medallion Architecture from Databricks
- + File I/O, pathlib, and JSON in Python from Python
- + Error Handling in Python from Python
- + Terraform Fundamentals (IaC) from Terraform
Data Governance: Catalogs, Classification, and Access Policy
Classify every column in a catalog by sensitivity, enforce role-based access against it, and watch three roles get three different answers to one query.
By SkillByExample · Published
Classify four columns by sensitivity, then run the same query as three different roles and watch access get enforced column-by-column, not table-by-table.
A catalog: every column, classified once
CATALOG = {
"gold.customers": {
"customer_id": "public",
"signup_country": "internal",
"email": "confidential",
"ssn": "restricted",
},
"gold.daily_revenue": {
"order_date": "public",
"country_code": "public",
"revenue": "internal",
},
}
def describe_table(table):
print(f"{table}:")
for col, classification in CATALOG[table].items():
print(f" {col:<16} {classification}")
describe_table("gold.customers")
$ python governance.py
== catalog: every column classified ==
gold.customers:
customer_id public
signup_country internal
email confidential
ssn restricted
The classification lives with the column, decided once, independent of who ever asks for it.
Role clearance, defined separately from the catalog
ROLE_CLEARANCE = {
"analyst": {"public", "internal"},
"support_agent": {"public", "internal", "confidential"},
"compliance_officer": {"public", "internal", "confidential", "restricted"},
}
def enforce_access(role, table, requested_columns):
clearance = ROLE_CLEARANCE.get(role, set())
schema = CATALOG.get(table, {})
allowed, denied = [], []
for col in requested_columns:
classification = schema.get(col, "unknown")
if classification in clearance:
allowed.append(col)
else:
denied.append((col, classification))
return allowed, denied
The same query, three roles, three different results
print("== analyst wants email and ssn ==")
allowed, denied = enforce_access("analyst", "gold.customers", ["customer_id", "email", "ssn"])
print(f"allowed: {allowed}")
for col, classification in denied:
print(f"DENIED {col} (classification={classification}) - analyst role lacks clearance")
== access attempt: analyst wants email and ssn ==
allowed: ['customer_id']
DENIED email (classification=confidential) - analyst role lacks clearance
DENIED ssn (classification=restricted) - analyst role lacks clearance
print("== compliance_officer, same query ==")
allowed, denied = enforce_access("compliance_officer", "gold.customers", ["customer_id", "email", "ssn"])
print(f"allowed: {allowed}")
print(f"denied: {denied}")
== access attempt: same query, compliance_officer role ==
allowed: ['customer_id', 'email', 'ssn']
denied: []
print("== support_agent, same query ==")
allowed, denied = enforce_access("support_agent", "gold.customers", ["customer_id", "email", "ssn"])
print(f"allowed: {allowed}")
for col, classification in denied:
print(f"DENIED {col} (classification={classification}) - support_agent role lacks clearance")
== access attempt: support_agent, one column above clearance ==
allowed: ['customer_id', 'email']
DENIED ssn (classification=restricted) - support_agent role lacks clearance
One query, one function, three different results — because the decision is made per column
against a role’s clearance set, not as a single table-level yes/no. A support agent can see
enough to help a customer (email) without ever touching ssn; an analyst gets neither. No
one had to write three separate access rules — adding a fourth role only means defining its
clearance set once.
Practice
1. Add a new role, `marketing`, cleared only for "public", and query gold.daily_revenue's revenue column.
allowed: ['order_date', 'country_code']
DENIED revenue (classification=internal) - marketing role lacks clearance
revenue is internal, one level above marketing’s clearance — denied exactly like
ssn was for analyst, same mechanism, different column and role.
2. Query a column that doesn't exist in the catalog, e.g. "phone_number".
DENIED phone_number (classification=unknown) - analyst role lacks clearance
An uncatalogued column defaults to "unknown", which is in no role’s clearance set — fail
closed. A catalog gap should block access, not silently grant it.
3. Add a "legal_hold" classification that no role is ever cleared for, and add a column with it.
CATALOG["gold.customers"]["litigation_note"] = "legal_hold"
allowed, denied = enforce_access("compliance_officer", "gold.customers", ["litigation_note"])
print(allowed, denied)
[] [('litigation_note', 'legal_hold')]
Even the highest clearance in ROLE_CLEARANCE doesn’t include legal_hold unless someone
explicitly adds it to a role’s set — a genuinely un-grantable classification, useful for data
under active legal hold that no ordinary role should ever see.
4. Why enforce access per-column instead of just denying the whole query if any column is out of clearance?
Per-column enforcement lets a query return the columns it’s entitled to instead of failing
outright — the analyst still gets customer_id even though email and ssn were denied.
An all-or-nothing table-level check would block that analyst from gold.customers completely,
even for queries that only ever touch public columns.
Next: data security and PII — what to do with a column a role isn’t cleared to see in full, instead of just denying it outright.