AWS Lake Formation: Building Governed Data Lakes with Fine-Grained Access Control

An S3 data lake without governance is a compliance liability and an operational headache. IAM alone cannot give you column-level security on Parquet files. There is no central place to see who has access to what. Data discovery depends on asking colleagues rather than running queries. Sensitive columns like SSN and credit card numbers need the same access controls as a database, not a storage bucket.

AWS Lake Formation adds a governance layer on top of S3 and the Glue Data Catalog. It provides a central permissions model that works across Athena, Redshift Spectrum, Glue, and EMR, column-level and row-level security on catalog tables, tag-based access control at scale, and data lineage tracking. In this article I will walk through setting it up with Terraform and implementing fine-grained access control.

Enabling Lake Formation and Registering S3 Locations

resource "aws_lakeformation_data_lake_settings" "main" {
  admins = [
    aws_iam_role.lake_formation_admin.arn,
    data.aws_caller_identity.current.arn
  ]

  create_database_default_permissions {
    permissions = []
    principal   = "IAM_ALLOWED_PRINCIPALS"
  }

  create_table_default_permissions {
    permissions = []
    principal   = "IAM_ALLOWED_PRINCIPALS"
  }
}

resource "aws_lakeformation_resource" "data_lake" {
  arn      = aws_s3_bucket.data_lake.arn
  role_arn = aws_iam_role.lake_formation_s3.arn
}

resource "aws_lakeformation_resource" "curated" {
  arn      = "${aws_s3_bucket.data_lake.arn}/curated/"
  role_arn = aws_iam_role.lake_formation_s3.arn
}

Setting create_database_default_permissions and create_table_default_permissions to empty arrays for IAM_ALLOWED_PRINCIPALS is the critical first step. By default, Lake Formation grants all permissions to IAM_ALLOWED_PRINCIPALS, which means existing IAM policies still control access and Lake Formation does nothing. Clearing these defaults forces all access to go through Lake Formation permissions. Do not skip this step or your governance layer has no effect.

LF-Tags for Attribute-Based Access Control

resource "aws_lakeformation_lf_tag" "sensitivity" {
  key    = "Sensitivity"
  values = ["public", "internal", "confidential", "restricted"]
}

resource "aws_lakeformation_lf_tag" "domain" {
  key    = "Domain"
  values = ["orders", "customers", "products", "finance", "hr"]
}

resource "aws_lakeformation_resource_lf_tags" "orders_table" {
  table {
    database_name = "curated_data"
    name          = "orders"
  }

  lf_tag {
    key   = "Sensitivity"
    value = "internal"
  }

  lf_tag {
    key   = "Domain"
    value = "orders"
  }
}

resource "aws_lakeformation_resource_lf_tags" "customers_pii_columns" {
  table_with_columns {
    database_name = "curated_data"
    name          = "customers"
    column_names  = ["email", "phone", "address", "date_of_birth"]
  }

  lf_tag {
    key   = "Sensitivity"
    value = "restricted"
  }
}

resource "aws_lakeformation_tag_based_permissions" "analyst_orders" {
  principal     = aws_iam_role.data_analyst.arn
  permissions   = ["SELECT"]
  catalog_id    = data.aws_caller_identity.current.account_id

  lf_tag_policy {
    resource_type = "TABLE"

    expression {
      key    = "Sensitivity"
      values = ["public", "internal"]
    }

    expression {
      key    = "Domain"
      values = ["orders", "products"]
    }
  }
}

resource "aws_lakeformation_tag_based_permissions" "pii_team" {
  principal   = aws_iam_role.pii_analyst.arn
  permissions = ["SELECT"]
  catalog_id  = data.aws_caller_identity.current.account_id

  lf_tag_policy {
    resource_type = "COLUMN"

    expression {
      key    = "Sensitivity"
      values = ["public", "internal", "confidential", "restricted"]
    }
  }
}

LF-Tags let you grant access by attribute rather than by specific table name. The data analyst role gets SELECT on any table tagged Sensitivity=public or internal in the orders or products domains. As new tables are created and tagged, they automatically fall into or outside this permission without any permission updates. This scales to hundreds of tables and dozens of teams without permission sprawl.

Row-Level Security

resource "aws_lakeformation_data_cells_filter" "emea_orders" {
  table_data {
    database_name    = "curated_data"
    name             = "orders"
    table_catalog_id = data.aws_caller_identity.current.account_id

    filter_name = "emea-region-filter"

    row_filter {
      filter_expression = "region = 'EMEA'"
    }

    column_names = ["order_id", "customer_id", "order_date", "amount", "status", "region"]
  }
}

resource "aws_lakeformation_permissions" "emea_analyst_filtered" {
  principal   = aws_iam_role.emea_analyst.arn
  permissions = ["SELECT"]

  data_cells_filter {
    database_name = "curated_data"
    table_name    = "orders"
    name          = "emea-region-filter"
    table_catalog_id = data.aws_caller_identity.current.account_id
  }
}

Data cells filters combine row filtering and column projection. The EMEA analyst role can only see rows where region = EMEA and only the six columns specified. Any Athena query this role runs against the orders table automatically has these filters applied server-side before results are returned. The analyst cannot see other regions or excluded columns regardless of how they write the query.

Closing Thoughts

Lake Formation is the governance infrastructure that makes a data lake enterprise-ready. Without it you have S3 storage with Glue metadata, which is a starting point. With it you have a governed platform where access control is centralized, auditable, and scalable across hundreds of datasets and dozens of teams.

Clear the default IAM_ALLOWED_PRINCIPALS permissions immediately after enabling Lake Formation. Use LF-Tags for scalable attribute-based access control. Apply data cells filters for teams that should only see a subset of rows or columns. The governance layer pays back in audit confidence and compliance clarity at every data review thereafter.

Enjoy the cloud.

Osama


#AWS #LakeFormation #DataLake #DataGovernance #DataEngineering #CloudArchitecture #Terraform #InfrastructureAsCode #AmazonWebServices #SolutionsArchitect #CloudComputing #Athena #Glue #DataSecurity #TechBlog #CloudInfrastructure #ABAC #FineSecurity #Analytics #Compliance

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.