Skip to content

Feature/azure network redesign #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6872e1f
feat(azure): Add support for SAT
connorbrown-db Mar 5, 2025
80f75b9
tests(azure): Reenable tags checking, require terraform fmt in tests
connorbrown-db Mar 5, 2025
a01b508
fix(azure): Add various missing tags
connorbrown-db Mar 5, 2025
59af995
style(azure): various whitespace/styling updates
connorbrown-db Mar 17, 2025
a6bb325
feat(azure): Remove default storage from metastore
connorbrown-db Mar 17, 2025
566238f
feat(azure): Add catalog module
connorbrown-db Mar 21, 2025
338cff6
feat(azure): Remove for_each spoke creation
connorbrown-db Mar 21, 2025
83fe181
feat(azure): Add default catalog for spoke
connorbrown-db Mar 21, 2025
12e845c
chore(azure): Terraform fmt
connorbrown-db Mar 21, 2025
0acabc9
fix(azure): Make all SAT resources use the same azure provider
connorbrown-db Mar 25, 2025
c7921de
style(azure): Rename local.sat_spoke to local.sat_workspace
connorbrown-db Mar 27, 2025
7a25352
docs(azure): Update README with SAT details
connorbrown-db Mar 27, 2025
4b684e4
feat(azure): Switch to pessimistic pin for naming module
connorbrown-db Mar 27, 2025
dd733e3
feat(azure): Provision webauth workspace as a normal workspace, now s…
connorbrown-db Apr 4, 2025
f1791e6
feat(azure): Default SAT to the hub webauth workspace
connorbrown-db Apr 9, 2025
ffeacac
feat(azure): Remove dedicated SAT catalog and provider
connorbrown-db Apr 14, 2025
bebb806
docs(azure): Improve comments and README
connorbrown-db Apr 14, 2025
95fac1c
fix(azure): CMK access policy dependency moved to correct access policy
connorbrown-db Apr 15, 2025
762493d
tests(azure): Replace sat spoke test with nondefault test
connorbrown-db Apr 15, 2025
c3dd063
feat(azure): Add better support for resource_suffix on SAT catalog
connorbrown-db Apr 16, 2025
f021b36
feat(azure): Add azure management lock to webauth workspace to preven…
connorbrown-db Apr 16, 2025
a5d8f91
feat(azure): Allow dynamic dependency for SAT catalog on SAT module
connorbrown-db Apr 16, 2025
8620b7f
feat(azure): Bump version of SAT module
connorbrown-db Apr 16, 2025
9bc7438
fix(azure): Make webauth workspace use hub firewall
connorbrown-db Apr 18, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/azure-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ jobs:
uses: ./.github/workflows/terraform-ruw.yml
with:
working_directory: azure/tf
tflint_args: "--config=$(pwd)/.tflint.hcl"
terraform_fmt_check: true
221 changes: 220 additions & 1 deletion azure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
7. From `tf` directory, run `terraform plan -var-file <YOUR_VAR_FILE>`, if edited directly, the command would be `terraform plan -var-file template.tfvars.example`
8. Run `terraform apply -var-file <YOUR_VAR_FILE`

## Note on provider initialization
## Note on provider initialization with Azure CLI
If you are using [Azure CLI Authentication](https://registry.terraform.io/providers/databricks/databricks/latest/docs#authenticating-with-azure-cli),
you may encounter an error like the below:

Expand All @@ -28,6 +28,23 @@ export ARM_TENANT_ID="00000000-0000-0000-0000-000000000000"

Alternatively, you can set the tenant ID in the databricks provider configurations (see the provider [doc](https://registry.terraform.io/providers/databricks/databricks/latest/docs#special-configurations-for-azure) for more info.)

You may also encounter errors like the below when Terraform begins provisioning workspace resources:

```shell
│ Error: cannot read current user: Unauthorized access to Org: 0000000000000000
│ with module.sat[0].module.sat.data.databricks_current_user.me,
│ on .terraform/modules/sat.sat/terraform/common/data.tf line 1, in data "databricks_current_user" "me":
│ 1: data "databricks_current_user" "me" {}
```

To fix this error, log in to the newly created spoke workspace by clicking on the "Launch Workspace" button in the Azure
portal. This must be done as the user who is running this Terraform, or the user running this Terraform must be granted
workspace admin after the first user launches the workspace.

# Introduction

Databricks has worked with thousands of customers to securely deploy the Databricks platform with appropriate security features to meet their architecture requirements.
Expand Down Expand Up @@ -67,6 +84,208 @@ can be controlled to reduce your threat vector. The AWS directory contains examp
monitor cost and accurately attribute Databricks usage to your organization's business unit and teams (for chargebacks, for examples). These tags propagate to detailed
DBU usage reports for cost analysis.

## Security Analysis Tool
Security Analysis Tool ([SAT](https://github.com/databricks-industry-solutions/security-analysis-tool/tree/main)) is enabled by default. It can be customized using the `sat_configuration` variable.
By default, SAT is installed in the hub workspace, also called the "WEB_AUTH" workspace.

### Changing the SAT workspace
To change which workspace SAT is installed in, there are three modifications required to the `customizations.tf`:

1. Change the Databricks provider used in the `SAT` module to use a different workspace
```hcl
# customizations.tf - default
# Default

# Change the provider if needed
providers = {
databricks = databricks.hub #<---- This can be modified
}
```

```hcl
# customizations.tf - modified

# Change the provider if needed
providers = {
databricks = databricks.spoke
}
```

2. Change the "sat_workspace" local to use the correct module
```hcl
# customizations.tf - default
locals {
sat_workspace = module.hub #<- This should be updated to the spoke you would like to use for SAT
}
```
```hcl
# customizations.tf - modified
locals {
sat_workspace = module.spoke #<- This should be updated to the spoke you would like to use for SAT
}
```

3. Change the `databricks_permission_assignment.sat_workspace_admin` resource to use the correct provider
```hcl
# customizations.tf - default
resource "databricks_permission_assignment" "sat_workspace_admin" {
count = length(module.sat)
...
provider = databricks.hub #<- This should be updated to the spoke you would like to use for SAT
}
```
```hcl
# customizations.tf - modified
resource "databricks_permission_assignment" "sat_workspace_admin" {
count = length(module.sat)
...
provider = databricks.spoke
}
```
Note that SAT is designed to be deployed _once per Azure subscription_. If needed, SAT can be deployed multiple times in
different regions using this terraform configuration. This requires provisioning SAT in multiple spokes. Reference the
above modifications to deploy to multiple spokes.

### SAT Service Principal
Some users of SRA may not have permissions to create Entra ID service principals. If this is the case, you can choose to
bring-your-own service principal. To configure a pre-existing Entra ID service principal to be used for SAT, configure
the `sat_service_principal` variable like the example below:

```hcl
# example.tfvars
sat_service_principal = {
client_id = "00000000-0000-0000-0000-000000000000"
client_secret = "some-secret"
}
```

If you do not bring-your-own service principal, an Entra ID service principal will be created for you with a default
name of `spSAT`. This name can be customized by modifying the `sat_service_principal` variable like so:
```hcl
# example.tfvars
sat_service_principal = {
name = "spSATDev"
}
```

### SAT Serverless Compute
SAT is installed using serverless compute by default. Before running the [required jobs](https://github.com/databricks-industry-solutions/security-analysis-tool/blob/v0.3.3/terraform/azure/TERRAFORM_Azure.md#step-7-run-databricks-jobs)
in Databricks, the private endpoints on your hub storage account must be approved.

## Adding additional spokes

To add additional spokes to this configuration, follow the below steps.

1. Add a new key to the spoke_config variable

```hcl
# Terraform variables (for example, terraform.tfvars)
spoke_config = {
spoke = {
resource_suffix = "spoke"
cidr = "10.1.0.0/20"
tags = {
environment = "dev"
},
spoke_b = { #<----- Add a new spoke config
resource_suffix = "spoke_b"
cidr = "10.2.0.0/20"
tags = {
environment = "test"
}
}
}
```

2. Add a new provider to the providers.tf for the new spoke

```hcl
# providers.tf

# New spoke provider
provider "databricks" {
alias = "spoke_b"
host = module.spoke_b.workspace_url
}
```

3. Copy the `spoke.tf` file to a new file (for example, `spoke_b.tf`).

4. Make the following adjustments to the new file

```hcl
# spoke_b.tf
module "spoke" { #<----- Modify the name of the module to something unique
source = "./modules/spoke"

# Update these per spoke
resource_suffix = var.spoke_config["spoke"].resource_suffix #<----- Use a new key in the spoke_config variable
vnet_cidr = var.spoke_config["spoke"].cidr
tags = var.spoke_config["spoke"].tags

...

depends_on = [module.hub]
}

module "spoke_catalog" { #<----- Rename this spoke's catalog to something unique
source = "./modules/catalog"

# Update these per catalog for the catalog's spoke
catalog_name = module.spoke.resource_suffix #<----- Replace all references to original spoke with new spoke
dns_zone_ids = [module.spoke.dns_zone_ids["dfs"]]
ncc_id = module.spoke.ncc_id
resource_group_name = module.spoke.resource_group_name
resource_suffix = module.spoke.resource_suffix
subnet_id = module.spoke.subnet_ids.privatelink
tags = module.spoke.tags

...

providers = {
databricks.workspace = databricks.spoke #<----- Replace provider reference to new spoke
}
}
```

```hcl
# spoke_b.tf - modified
module "spoke_b" {
source = "./modules/spoke"

# Update these per spoke
resource_suffix = var.spoke_config["spoke_b"].resource_suffix
vnet_cidr = var.spoke_config["spoke_b"].cidr
tags = var.spoke_config["spoke_b"].tags

...

depends_on = [module.hub]
}

module "spoke_b_catalog" {
source = "./modules/catalog"

# Update these per catalog for the catalog's spoke
catalog_name = module.spoke_b.resource_suffix
dns_zone_ids = [module.spoke_b.dns_zone_ids["dfs"]]
ncc_id = module.spoke_b.ncc_id
resource_group_name = module.spoke_b.resource_group_name
resource_suffix = module.spoke_b.resource_suffix
subnet_id = module.spoke_b.subnet_ids.privatelink
tags = module.spoke_b.tags

...

providers = {
databricks.workspace = databricks.spoke_b
}
}

```

5. Run `terraform apply` to create the new spoke

# Additional Security Recommendations and Opportunities

In this section, we break down additional security recommendations and opportunities to maintain a strong security posture that either cannot be configured into this
Expand Down
6 changes: 3 additions & 3 deletions azure/tf/.tflint.hcl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
config {
variables = ["tags={\"foo\"=\"bar\"}"]
varfile = ["tests/terraform.tfvars"]
}

plugin "terraform" {
Expand All @@ -13,6 +13,6 @@ plugin "azurerm" {
}

rule "azurerm_resource_missing_tags" {
enabled = false
tags = ["foo"]
enabled = true
tags = ["example"]
}
89 changes: 89 additions & 0 deletions azure/tf/customizations.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
locals {
create_sat_sp = var.sat_configuration.enabled && var.sat_service_principal.client_id == ""
sat_client_id = local.create_sat_sp ? azuread_service_principal.sat[0].client_id : var.sat_service_principal.client_id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT @connorbrown-db Maybe we should consider renaming the SP so that it's non-exclusive to SAT. Future customizations may leverage the same SP together with the SAT deployment

sat_client_secret = local.create_sat_sp ? azuread_service_principal_password.sat[0].value : var.sat_service_principal.client_secret
sat_workspace = module.hub
sat_catalog = var.sat_configuration.enabled ? module.hub_catalog[0] : {}
}

# ----------------------------------------------------------------------------------------------------------------------
# Service Principal for SAT
# Note: This is separated from the SAT module to allow for a BYO-SP pattern. If the user supplies values for the
# sat_service principal variable, creation will be skipped.

resource "azuread_application_registration" "sat" {
count = local.create_sat_sp ? 1 : 0

display_name = var.sat_service_principal.name
}

resource "azuread_service_principal" "sat" {
count = local.create_sat_sp ? 1 : 0

client_id = azuread_application_registration.sat[0].client_id
owners = [data.azurerm_client_config.current.object_id]
}

resource "azuread_service_principal_password" "sat" {
count = local.create_sat_sp ? 1 : 0

service_principal_id = azuread_service_principal.sat[0].id
}

data "azurerm_subscription" "sat" {
count = local.create_sat_sp ? 1 : 0

subscription_id = var.subscription_id
}

resource "azurerm_role_assignment" "sat_can_read_subscription" {
count = local.create_sat_sp ? 1 : 0

principal_id = azuread_service_principal.sat[0].object_id
scope = data.azurerm_subscription.sat[0].id
role_definition_name = "Reader"
}

# ----------------------------------------------------------------------------------------------------------------------
# This is modularized to allow for easy count and provider arguments
module "sat" {
source = "./modules/sat"
count = var.sat_configuration.enabled ? 1 : 0

# Update this as needed
catalog_name = local.sat_catalog.catalog_name

tenant_id = data.azurerm_client_config.current.tenant_id
subscription_id = var.subscription_id
databricks_account_id = var.databricks_account_id
schema_name = var.sat_configuration.schema_name
proxies = var.sat_configuration.proxies
run_on_serverless = var.sat_configuration.run_on_serverless
service_principal_client_id = local.sat_client_id
service_principal_client_secret = local.sat_client_secret
workspace_id = local.sat_workspace.workspace_id

depends_on = [local.sat_catalog]

# Change the provider if needed
providers = {
databricks = databricks.hub
}
}

# Grant the SP created by SAT the account_admin role
resource "databricks_service_principal_role" "sat_account_admin" {
count = length(module.sat)

role = "account_admin"
service_principal_id = module.sat[0].service_principal_id
}

resource "databricks_permission_assignment" "sat_workspace_admin" {
count = length(module.sat)

permissions = ["ADMIN"]
principal_id = module.sat[0].service_principal_id

provider = databricks.hub
}
Loading