Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 27 additions & 33 deletions docs/tutorials/manage-data-masking-with-terraform.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This tutorial is part of the **Bytebase Terraform Provider** series:
- **Configure** data classification levels and categories
- **Create** global masking policies that apply workspace-wide
- **Set up** database-specific column masking
- **Grant** masking exceptions for specific users
- **Grant** masking exemptions for specific users

## Prerequisites

Expand Down Expand Up @@ -146,40 +146,42 @@ resource "bytebase_setting" "classification" {
title = "Classification Example"

levels {
id = "1"
title = "Level 1"
level = 1
}
levels {
id = "2"
title = "Level 2"
level = 2
}

classifications {
id = "1"
title = "Basic"
level = 1
}

classifications {
id = "1-1"
title = "User basic"
level = "1"
level = 1
}

classifications {
id = "1-2"
title = "User contact info"
level = "2"
level = 2
}

classifications {
id = "2"
title = "Employment"
level = 1
}

classifications {
id = "2-1"
title = "Employment info"
level = "2"
level = 2
}
}
}
Expand Down Expand Up @@ -227,29 +229,29 @@ resource "bytebase_policy" "global_masking_policy" {
bytebase_setting.environments
]

parent = "workspaces/-"
# parent defaults to workspace when not specified.
type = "MASKING_RULE"
enforce = true
inherit_from_parent = false

global_masking_policy {

rules {
condition = "column_name == \"birth_date\""
condition = "resource.column_name == \"birth_date\""
id = "birth-date-mask"
semantic_type = "date-year-mask"
title = "Mask Birth Date Year"
}

rules {
condition = "column_name == \"last_name\""
condition = "resource.column_name == \"last_name\""
id = "last-name-first-letter-only"
semantic_type = "name-first-letter-only"
title = "Last Name Only Show First Letter"
}

rules {
condition = "classification_level in [\"2\"]"
condition = "resource.classification_level == 2"
id = "classification-level-2"
semantic_type = "full-mask" # Maps Level 2 classification to full-mask semantic type
title = "Full Mask for Classification Level 2"
Expand Down Expand Up @@ -339,50 +341,42 @@ Verify in Bytebase:

![dev1-salary](/content/docs/tutorials/manage-data-masking-with-terraform/bb-dev1-salary.webp)

## Step 6: Grant Masking Exceptions
## Step 6: Grant Masking Exemptions

| | |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Terraform resource | [bytebase_policy](https://registry.terraform.io/providers/bytebase/bytebase/latest/docs/resources/policy) |
| Sample file | [8-5-masking-exception.tf](https://github.com/bytebase/terraform-provider-bytebase/blob/main/tutorials/8-5-masking-exception.tf) |
| Sample file | [8-5-masking-exemption.tf](https://github.com/bytebase/terraform-provider-bytebase/blob/main/tutorials/8-5-masking-exemption.tf) |

Create `8-5-masking-exception.tf` to grant bypass permissions:
Create `8-5-masking-exemption.tf` to grant bypass permissions:

- Workspace Admin (`admin@example.com`) has Masking Exemptions for `birth_date` in table `employee` for Query
- Workspace Admin (`admin@example.com`) has Masking Exemptions for `last_name` in table `employee` for Export
- Workspace Admin (`admin@example.com`) and QA Tester (`qa1@example.com`) have masking exemptions for `birth_date` and `last_name` in table `employee`
- Developer 1 (`dev1@example.com`) has exemption for specific columns via CEL expression

```hcl 8-5-masking-exception.tf
resource "bytebase_policy" "masking_exception_policy" {
```hcl 8-5-masking-exemption.tf
resource "bytebase_policy" "masking_exemption_policy" {
depends_on = [
bytebase_project.project-two,
bytebase_instance.prod
]

parent = bytebase_project.project-two.name
Comment thread
adela-bytebase marked this conversation as resolved.
type = "MASKING_EXCEPTION"
type = "MASKING_EXEMPTION"
enforce = true
inherit_from_parent = false

masking_exception_policy {
exceptions {
masking_exemption_policy {
exemptions {
reason = "Business requirement"
database = "instances/prod-sample-instance/databases/hr_prod"
table = "employee"
columns = ["birth_date", "last_name"]
members = ["user:admin@example.com"]
actions = ["QUERY", "EXPORT"]
expire_timestamp = "2027-07-30T16:11:49Z"
}
exceptions {
reason = "Export data for analysis"
members = ["user:qa1@example.com"]
actions = ["EXPORT"]
members = ["user:admin@example.com", "user:qa1@example.com"]
expire_timestamp = "2027-07-30T16:11:49Z"
}
exceptions {
exemptions {
reason = "Grant query access"
members = ["user:dev1@example.com"]
actions = ["QUERY"]
members = ["user:dev1@example.com"]
raw_expression = "resource.instance_id == \"prod-sample-instance\" && resource.database_name == \"hr_prod\" && resource.table_name == \"employee\" && resource.column_name in [\"first_name\", \"last_name\", \"gender\"]"
}
}
Expand All @@ -407,14 +401,14 @@ If you specify `raw_expression`, it defines the exemption condition directly as

## Step 7: Apply Final Configuration and Test

Apply the masking exceptions and test everything:
Apply the masking exemptions and test everything:

```bash
terraform plan
terraform apply
```

Verify the masking exceptions are working:
Verify the masking exemptions are working:

1. Log in as Workspace Admin (`admin@example.com`), then go to **SQL Editor** to access `hr_prod`, double-click `employee` table on the left. You may notice the `birth_date` is not masked any longer.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,37 +72,40 @@ Create `7-1-workspace-iam.tf` with workspace-level permissions:
resource "bytebase_iam_policy" "workspace_iam" {
depends_on = [
bytebase_user.workspace_admin,
bytebase_user.tf_service_account,
bytebase_service_account.tf_service_account,
bytebase_workload_identity.github_ci,
bytebase_user.workspace_dba1,
bytebase_user.workspace_dba2,
bytebase_group.qa
]

parent = "workspaces/-"
# parent defaults to workspace when not specified.

iam_policy {

binding {
role = "roles/workspaceAdmin"
members = [
format("user:%s", bytebase_user.workspace_admin.email),
format("user:%s", bytebase_user.tf_service_account.email),
# Keep the Terraform-running service account as Workspace Admin so
# subsequent `terraform apply` runs retain full permissions.
format("serviceAccount:%s", bytebase_service_account.tf_service_account.email),
]
}

binding {
role = "roles/workspaceDBA"
members = [
format("user:%s", bytebase_user.workspace_dba1.email),
format("user:%s", bytebase_user.workspace_dba2.email)
format("user:%s", bytebase_user.workspace_dba2.email),
format("workloadIdentity:%s", bytebase_workload_identity.github_ci.email),
]
}

binding {
role = "roles/workspaceMember"
members = [
"allUsers"
# Note: a special member representing everyone in the workspace
]
}

Expand All @@ -111,7 +114,6 @@ resource "bytebase_iam_policy" "workspace_iam" {
members = [
format("group:%s", bytebase_group.qa.email),
]
# Note: This grants projectViewer role to ALL projects in the workspace
}
}
}
Expand All @@ -134,7 +136,7 @@ terraform apply
1. Go to **IAM & Admin > Members**
1. Check user roles:
- `admin@example.com`: Workspace Admin
- `tf@service.bytebase.com`: Workspace Admin
- `tf@service.bytebase.com` (service account): Workspace Admin
- `dba@example.com`, `dba2@example.com`: Workspace DBA
- `allUsers`: Workspace Member
1. Note that `QA Team` group has `Project Viewer` role for ALL projects
Expand Down
4 changes: 2 additions & 2 deletions docs/tutorials/manage-databases-with-terraform.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ resource "bytebase_instance" "test" {

# Connection settings for the built-in test database
data_sources {
id = "admin-test"
id = "admin data source test-sample-instance"
type = "ADMIN"
host = "/tmp" # Unix socket for local connection
port = "8083"
Expand All @@ -105,7 +105,7 @@ resource "bytebase_instance" "prod" {
activation = true

data_sources {
id = "admin-prod"
id = "admin data source prod-sample-instance"
type = "ADMIN"
host = "/tmp"
port = "8084"
Expand Down
69 changes: 14 additions & 55 deletions docs/tutorials/manage-environments-with-terraform.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Navigate to **Environments** in Bytebase. You'll see two default environments: `
required_providers {
bytebase = {
source = "registry.terraform.io/bytebase/bytebase"
version = "3.8.6" # Check for latest version
version = "3.17.1" # Check for latest version
}
}
}
Expand Down Expand Up @@ -206,7 +206,7 @@ When no rollout policy is found for an environment, Bytebase applies a default r
You can explicitly configure these policies using Terraform. Create `1-2-env-policy-rollout.tf`:

```hcl 1-2-env-policy-rollout.tf
# Test environment - automatic deployment with default checkers
# Test environment - automatic deployment
resource "bytebase_policy" "rollout_policy_test" {
depends_on = [bytebase_setting.environments]
parent = bytebase_setting.environments.environment_setting[0].environment[0].name
Expand All @@ -216,22 +216,12 @@ resource "bytebase_policy" "rollout_policy_test" {
automatic = true # Deploy changes automatically
roles = [
"roles/workspaceAdmin",
"roles/projectOwner",
"roles/LAST_APPROVER",
"roles/CREATOR"
"roles/projectOwner"
]

# Default checkers (explicitly configured)
checkers {
required_issue_approval = true
required_status_checks {
plan_check_enforcement = "ERROR_ONLY" # Block on errors only
}
}
}
}

# Production - manual deployment with stricter checks
# Production - manual deployment
resource "bytebase_policy" "rollout_policy_prod" {
depends_on = [bytebase_setting.environments]
parent = bytebase_setting.environments.environment_setting[0].environment[1].name
Expand All @@ -241,18 +231,8 @@ resource "bytebase_policy" "rollout_policy_prod" {
automatic = false # Require manual deployment
roles = [
"roles/workspaceAdmin",
"roles/projectOwner",
"roles/LAST_APPROVER",
"roles/CREATOR"
"roles/projectOwner"
]

# Enforce all plan checks (errors and warnings)
checkers {
required_issue_approval = true
required_status_checks {
plan_check_enforcement = "STRICT" # Block on both errors and warnings
}
}
}
}
```
Expand All @@ -261,11 +241,6 @@ resource "bytebase_policy" "rollout_policy_prod" {

- `automatic`: When `true`, changes deploy automatically after approval. When `false`, requires manual click to deploy.
- `roles`: List of roles allowed to manually deploy changes. Required even with automatic rollout, as manual approval is needed when checks fail.
- `checkers.required_issue_approval`: When `true`, requires issue approval before rollout.
- `checkers.required_status_checks.plan_check_enforcement`: Controls SQL plan check enforcement:
- `PLAN_CHECK_ENFORCEMENT_UNSPECIFIED`: Allow rollout regardless of plan check results (no enforcement)
- `ERROR_ONLY`: Block rollout only when plan check finds errors (default)
- `STRICT`: Block rollout when plan check finds errors or warnings (stricter for production)

### Data Protection Policy

Expand All @@ -277,38 +252,22 @@ resource "bytebase_policy" "rollout_policy_prod" {
Create `1-3-env-policy-data.tf`:

```hcl 1-3-env-policy-data.tf
# Prevent copying data in SQL Editor from production
resource "bytebase_policy" "disable_copy_data_policy_prod" {
depends_on = [bytebase_setting.environments]
parent = bytebase_setting.environments.environment_setting[0].environment[1].name
type = "DISABLE_COPY_DATA"

disable_copy_data_policy {
enable = true # Block data copy
}
}

# Restrict SQL execution in SQL Editor from production
resource "bytebase_policy" "data_source_query_policy_prod" {
# Restrict SQL Editor data access on production
resource "bytebase_policy" "query_data_policy_prod" {
depends_on = [bytebase_setting.environments]
parent = bytebase_setting.environments.environment_setting[0].environment[1].name
type = "DATA_SOURCE_QUERY"
type = "DATA_QUERY"

data_source_query_policy {
restriction = "RESTRICTION_UNSPECIFIED"
disallow_ddl = true # No schema changes via SQL Editor
disallow_dml = true # No data changes via SQL Editor
query_data_policy {
maximum_result_rows = 1000
disable_copy_data = true # Block data copy
disable_export = true # Block data export
allow_admin_data_source = false # Restrict admin data source access
}
}
```

- Here data protection policy is only applied to the `Prod` environment. Which means in `Test` environment, by default, users may execute DDL and DML statements or copy data directly in SQL Editor.

- `restriction` controls [access to the data source](/sql-editor/settings/data-source-restriction):

- `RESTRICTION_UNSPECIFIED`: Admin data source is allowed.
- `DISALLOW`: Admin data source is completely disallowed.
- `FALLBACK`: Prefer the read-only data source; use admin only if read-only is not configured.
- The data protection policy is only applied to the `Prod` environment. In the `Test` environment, by default, users may execute queries and copy data directly in SQL Editor.

## Step 6 - Apply Configuration

Expand Down
Loading
Loading