diff --git a/.nextchanges/bundles/warn-run-as-file-access.md b/.nextchanges/bundles/warn-run-as-file-access.md new file mode 100644 index 00000000000..9c920aac813 --- /dev/null +++ b/.nextchanges/bundles/warn-run-as-file-access.md @@ -0,0 +1 @@ +* Warn when a bundle's `run_as` identity may lack access to deployed files, with guidance for top-level permissions and source-linked files. ([#7313](https://github.com/databricks/cli/pull/7313)) diff --git a/acceptance/bundle/migrate/command/runas/output.txt b/acceptance/bundle/migrate/command/runas/output.txt index c17ffdec425..ff7c313ea5b 100644 --- a/acceptance/bundle/migrate/command/runas/output.txt +++ b/acceptance/bundle/migrate/command/runas/output.txt @@ -4,6 +4,14 @@ >>> print_requests.py //pipelines >>> print_state.py +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:22:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + Recommendation: permissions section should explicitly include the current deployment identity '[USERNAME]' or one of its groups If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy. @@ -21,6 +29,14 @@ Notice: migrating your bundle to direct deployment engine (https://github.com/da >>> [CLI] pipelines get [UUID] >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle deployment migrate +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:22:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + Recommendation: permissions section should explicitly include the current deployment identity '[USERNAME]' or one of its groups If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy. @@ -45,6 +61,14 @@ To undo the migration, remove [TEST_TMP_DIR]/.databricks/bundle/production/resou >>> print_state.py >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle plan +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:22:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + Recommendation: permissions section should explicitly include the current deployment identity '[USERNAME]' or one of its groups If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy. @@ -60,6 +84,14 @@ See https://docs.databricks.com/dev-tools/bundles/permissions.html to learn more Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle plan -o json +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:22:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + Recommendation: permissions section should explicitly include the current deployment identity '[USERNAME]' or one of its groups If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy. @@ -74,6 +106,14 @@ See https://docs.databricks.com/dev-tools/bundles/permissions.html to learn more >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle destroy --auto-approve +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:22:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + Recommendation: permissions section should explicitly include the current deployment identity '[USERNAME]' or one of its groups If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy. diff --git a/acceptance/bundle/run_as/allowed/regular_user/output.txt b/acceptance/bundle/run_as/allowed/regular_user/output.txt index a326e670813..eba8238b68f 100644 --- a/acceptance/bundle/run_as/allowed/regular_user/output.txt +++ b/acceptance/bundle/run_as/allowed/regular_user/output.txt @@ -1,5 +1,13 @@ >>> [CLI] bundle validate -o json +Warning: run_as identity "my_service_principal" is not in bundle top-level permissions + in databricks.yml:5:3 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: my_service_principal + level: CAN_VIEW + >>> jq .run_as { @@ -38,6 +46,14 @@ } >>> [CLI] bundle validate -o json -t development +Warning: run_as identity "my_user_name" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: my_user_name + level: CAN_VIEW + >>> jq .run_as { diff --git a/acceptance/bundle/run_as/allowed/service_principal/output.txt b/acceptance/bundle/run_as/allowed/service_principal/output.txt index a326e670813..eba8238b68f 100644 --- a/acceptance/bundle/run_as/allowed/service_principal/output.txt +++ b/acceptance/bundle/run_as/allowed/service_principal/output.txt @@ -1,5 +1,13 @@ >>> [CLI] bundle validate -o json +Warning: run_as identity "my_service_principal" is not in bundle top-level permissions + in databricks.yml:5:3 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: my_service_principal + level: CAN_VIEW + >>> jq .run_as { @@ -38,6 +46,14 @@ } >>> [CLI] bundle validate -o json -t development +Warning: run_as identity "my_user_name" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: my_user_name + level: CAN_VIEW + >>> jq .run_as { diff --git a/acceptance/bundle/run_as/dashboard_embed/output.txt b/acceptance/bundle/run_as/dashboard_embed/output.txt index 5f316f521f6..c34d8c8ce0d 100644 --- a/acceptance/bundle/run_as/dashboard_embed/output.txt +++ b/acceptance/bundle/run_as/dashboard_embed/output.txt @@ -1,12 +1,20 @@ >>> errcode [CLI] bundle validate --var embed=false +Warning: run_as identity "sp-name" is not in bundle top-level permissions + in databricks.yml:5:3 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: sp-name + level: CAN_VIEW + Name: dashboard_embed Target: default Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/dashboard_embed/default -Validation OK! +Found 1 warning >>> errcode [CLI] bundle validate --var embed=true Error: dashboards with embed_credentials set to true do not support a setting a run_as user that is different from the owner. diff --git a/acceptance/bundle/run_as/empty_override/output.txt b/acceptance/bundle/run_as/empty_override/output.txt index 635d9eda9df..2ff5664cc30 100644 --- a/acceptance/bundle/run_as/empty_override/output.txt +++ b/acceptance/bundle/run_as/empty_override/output.txt @@ -1,2 +1,10 @@ +Warning: run_as identity "my_user_name" is not in bundle top-level permissions + in databricks.yml:5:3 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: my_user_name + level: CAN_VIEW + "run_as": { "run_as": { diff --git a/acceptance/bundle/run_as/empty_override/script b/acceptance/bundle/run_as/empty_override/script index de89daa9199..c97112e9ddb 100644 --- a/acceptance/bundle/run_as/empty_override/script +++ b/acceptance/bundle/run_as/empty_override/script @@ -1 +1 @@ -$CLI bundle validate -o json | grep run_as +$CLI bundle validate -o json | grep '"run_as"' diff --git a/acceptance/bundle/run_as/empty_run_as_dict/script b/acceptance/bundle/run_as/empty_run_as_dict/script index a8f5a36d672..d29883e42a6 100644 --- a/acceptance/bundle/run_as/empty_run_as_dict/script +++ b/acceptance/bundle/run_as/empty_run_as_dict/script @@ -1 +1 @@ -musterr $CLI bundle validate -o json | grep run_as +musterr $CLI bundle validate -o json | grep '"run_as"' diff --git a/acceptance/bundle/run_as/output.txt b/acceptance/bundle/run_as/output.txt index ca8c1b13bce..9307f9c122c 100644 --- a/acceptance/bundle/run_as/output.txt +++ b/acceptance/bundle/run_as/output.txt @@ -8,6 +8,14 @@ pipelines.pipeline_three: {"user_name":"user_override"} pipelines.pipeline_two: {"service_principal_name":"sp_override"} >>> [CLI] bundle validate -o json -t with_user_run_as +Warning: run_as identity "user_base" is not in bundle top-level permissions + in databricks.yml:39:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: user_base + level: CAN_VIEW + jobs.job_one: {"user_name":"user_base"} jobs.job_three: {"user_name":"user_override"} jobs.job_two: {"service_principal_name":"sp_override"} @@ -16,6 +24,14 @@ pipelines.pipeline_three: {"user_name":"user_override"} pipelines.pipeline_two: {"service_principal_name":"sp_override"} >>> [CLI] bundle validate -o json -t with_sp_run_as +Warning: run_as identity "sp_base" is not in bundle top-level permissions + in databricks.yml:43:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: sp_base + level: CAN_VIEW + jobs.job_one: {"service_principal_name":"sp_base"} jobs.job_three: {"user_name":"user_override"} jobs.job_two: {"service_principal_name":"sp_override"} diff --git a/acceptance/bundle/run_as/pipelines/regular_user/output.txt b/acceptance/bundle/run_as/pipelines/regular_user/output.txt index 21faafb4d57..00db450fdae 100644 --- a/acceptance/bundle/run_as/pipelines/regular_user/output.txt +++ b/acceptance/bundle/run_as/pipelines/regular_user/output.txt @@ -25,21 +25,53 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged \nProcessing target: t_user_name_different >>> [CLI] bundle validate -t t_user_name_different +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + Name: run_as Target: t_user_name_different Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/run_as/t_user_name_different -Validation OK! +Found 1 warning >>> [CLI] bundle plan -t t_user_name_different +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + create pipelines.nyc_taxi_pipeline create pipelines.nyc_taxi_pipeline.permissions Plan: 2 to add, 0 to change, 0 to delete, 0 unchanged +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + >>> [CLI] bundle deploy -t t_user_name_different +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as/t_user_name_different/files... Created pipelines.nyc_taxi_pipeline Created pipelines.nyc_taxi_pipeline.permissions @@ -48,21 +80,53 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged \nProcessing target: t_service_principal_name >>> [CLI] bundle validate -t t_service_principal_name +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:10:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + Name: run_as Target: t_service_principal_name Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/run_as/t_service_principal_name -Validation OK! +Found 1 warning >>> [CLI] bundle plan -t t_service_principal_name +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:10:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + create pipelines.nyc_taxi_pipeline create pipelines.nyc_taxi_pipeline.permissions Plan: 2 to add, 0 to change, 0 to delete, 0 unchanged +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:10:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + >>> [CLI] bundle deploy -t t_service_principal_name +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:10:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: [UUID] + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as/t_service_principal_name/files... Created pipelines.nyc_taxi_pipeline Created pipelines.nyc_taxi_pipeline.permissions @@ -71,21 +135,53 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged \nProcessing target: t_service_principal_name_different >>> [CLI] bundle validate -t t_service_principal_name_different +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + Name: run_as Target: t_service_principal_name_different Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/run_as/t_service_principal_name_different -Validation OK! +Found 1 warning >>> [CLI] bundle plan -t t_service_principal_name_different +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + create pipelines.nyc_taxi_pipeline create pipelines.nyc_taxi_pipeline.permissions Plan: 2 to add, 0 to change, 0 to delete, 0 unchanged +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + >>> [CLI] bundle deploy -t t_service_principal_name_different +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as/t_service_principal_name_different/files... Created pipelines.nyc_taxi_pipeline Created pipelines.nyc_taxi_pipeline.permissions diff --git a/acceptance/bundle/run_as/pipelines/service_principal/output.txt b/acceptance/bundle/run_as/pipelines/service_principal/output.txt index 21faafb4d57..29eee9029c6 100644 --- a/acceptance/bundle/run_as/pipelines/service_principal/output.txt +++ b/acceptance/bundle/run_as/pipelines/service_principal/output.txt @@ -2,21 +2,53 @@ === run_as should succeed in all cases\nProcessing target: t_user_name >>> [CLI] bundle validate -t t_user_name +Warning: run_as identity "tester@databricks.com" is not in bundle top-level permissions + in databricks.yml:7:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: tester@databricks.com + level: CAN_VIEW + Name: run_as Target: t_user_name Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/run_as/t_user_name -Validation OK! +Found 1 warning >>> [CLI] bundle plan -t t_user_name +Warning: run_as identity "tester@databricks.com" is not in bundle top-level permissions + in databricks.yml:7:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: tester@databricks.com + level: CAN_VIEW + create pipelines.nyc_taxi_pipeline create pipelines.nyc_taxi_pipeline.permissions Plan: 2 to add, 0 to change, 0 to delete, 0 unchanged +Warning: run_as identity "tester@databricks.com" is not in bundle top-level permissions + in databricks.yml:7:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: tester@databricks.com + level: CAN_VIEW + >>> [CLI] bundle deploy -t t_user_name +Warning: run_as identity "tester@databricks.com" is not in bundle top-level permissions + in databricks.yml:7:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: tester@databricks.com + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as/t_user_name/files... Created pipelines.nyc_taxi_pipeline Created pipelines.nyc_taxi_pipeline.permissions @@ -25,21 +57,53 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged \nProcessing target: t_user_name_different >>> [CLI] bundle validate -t t_user_name_different +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + Name: run_as Target: t_user_name_different Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/run_as/t_user_name_different -Validation OK! +Found 1 warning >>> [CLI] bundle plan -t t_user_name_different +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + create pipelines.nyc_taxi_pipeline create pipelines.nyc_taxi_pipeline.permissions Plan: 2 to add, 0 to change, 0 to delete, 0 unchanged +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + >>> [CLI] bundle deploy -t t_user_name_different +Warning: run_as identity "different@databricks.com" is not in bundle top-level permissions + in databricks.yml:13:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: different@databricks.com + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as/t_user_name_different/files... Created pipelines.nyc_taxi_pipeline Created pipelines.nyc_taxi_pipeline.permissions @@ -71,21 +135,53 @@ Resources: 2 created, 0 changed, 0 deleted, 0 unchanged \nProcessing target: t_service_principal_name_different >>> [CLI] bundle validate -t t_service_principal_name_different +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + Name: run_as Target: t_service_principal_name_different Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/run_as/t_service_principal_name_different -Validation OK! +Found 1 warning >>> [CLI] bundle plan -t t_service_principal_name_different +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + create pipelines.nyc_taxi_pipeline create pipelines.nyc_taxi_pipeline.permissions Plan: 2 to add, 0 to change, 0 to delete, 0 unchanged +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + >>> [CLI] bundle deploy -t t_service_principal_name_different +Warning: run_as identity "[UUID]" is not in bundle top-level permissions + in databricks.yml:16:7 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: [UUID] + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as/t_service_principal_name_different/files... Created pipelines.nyc_taxi_pipeline Created pipelines.nyc_taxi_pipeline.permissions diff --git a/acceptance/bundle/run_as/pipelines_legacy/output.txt b/acceptance/bundle/run_as/pipelines_legacy/output.txt index cfd58c5e866..d0debfb9b85 100644 --- a/acceptance/bundle/run_as/pipelines_legacy/output.txt +++ b/acceptance/bundle/run_as/pipelines_legacy/output.txt @@ -4,6 +4,14 @@ Warning: You are using the legacy mode of run_as. The support for this mode is e at experimental.use_legacy_run_as in databricks.yml:8:22 +Warning: run_as identity "my_service_principal" is not in bundle top-level permissions + in databricks.yml:5:3 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: my_service_principal + level: CAN_VIEW + >>> jq .run_as { diff --git a/acceptance/bundle/telemetry/deploy-experimental/output.txt b/acceptance/bundle/telemetry/deploy-experimental/output.txt index dcd99009312..d58493a2afb 100644 --- a/acceptance/bundle/telemetry/deploy-experimental/output.txt +++ b/acceptance/bundle/telemetry/deploy-experimental/output.txt @@ -4,6 +4,14 @@ Warning: You are using the legacy mode of run_as. The support for this mode is e at experimental.use_legacy_run_as in databricks.yml:5:22 +Warning: run_as identity "abcd" is not in bundle top-level permissions + in databricks.yml:8:3 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - user_name: abcd + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Created jobs.foo Files: 1 uploaded, 0 deleted diff --git a/acceptance/bundle/telemetry/deploy-run-as/output.txt b/acceptance/bundle/telemetry/deploy-run-as/output.txt index e6b42026853..b9f84a3d3a6 100644 --- a/acceptance/bundle/telemetry/deploy-run-as/output.txt +++ b/acceptance/bundle/telemetry/deploy-run-as/output.txt @@ -1,5 +1,13 @@ >>> [CLI] bundle deploy +Warning: run_as identity "sp_test" is not in bundle top-level permissions + in databricks.yml:5:3 + +Add a top-level permission so this identity can access deployed files and resources: + permissions: + - service_principal_name: sp_test + level: CAN_VIEW + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Created jobs.test_job Files: 1 uploaded, 0 deleted diff --git a/bundle/permissions/permission_diagnostics.go b/bundle/permissions/permission_diagnostics.go index 0e01f72a0f4..fce66c5b8ce 100644 --- a/bundle/permissions/permission_diagnostics.go +++ b/bundle/permissions/permission_diagnostics.go @@ -7,6 +7,8 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/iamutil" "github.com/databricks/cli/libs/set" @@ -29,14 +31,15 @@ func (m *permissionDiagnostics) Name() string { } func (m *permissionDiagnostics) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + diags := runAsAccessDiagnostics(b) + // Only recommend deployment-identity access when top-level permissions are explicit. if len(b.Config.Permissions) == 0 { - // Only warn if there is an explicit top-level permissions section - return nil + return diags } canManageBundle, _ := analyzeBundlePermissions(b) if canManageBundle { - return nil + return diags } me := b.Config.Workspace.CurrentUser.User @@ -45,7 +48,7 @@ func (m *permissionDiagnostics) Apply(ctx context.Context, b *bundle.Bundle) dia identityType = "service_principal_name" } - return diag.Diagnostics{{ + return diags.Append(diag.Diagnostic{ Severity: diag.Recommendation, Summary: fmt.Sprintf("permissions section should explicitly include the current deployment identity '%s' or one of its groups\n"+ "If it is not included, CAN_MANAGE permissions are only applied if the present identity is used to deploy.\n\n"+ @@ -60,6 +63,62 @@ func (m *permissionDiagnostics) Apply(ctx context.Context, b *bundle.Bundle) dia ), Locations: []diag.Location{b.Config.GetLocation("permissions")}, ID: diag.PermissionNotIncluded, + }) +} + +func runAsAccessDiagnostics(b *bundle.Bundle) diag.Diagnostics { + runAs := b.Config.RunAs + if runAs == nil { + return nil + } + + identityType, identity := "user_name", runAs.UserName + if runAs.ServicePrincipalName != "" { + identityType, identity = "service_principal_name", runAs.ServicePrincipalName + } else if runAs.GroupName != "" { + identityType, identity = "group_name", runAs.GroupName + } + + if identityType != "group_name" && identity == b.Config.Workspace.CurrentUser.UserName { + return nil + } + + if b.IsSourceLinkedDeployment() { + return diag.Diagnostics{{ + Severity: diag.Warning, + Summary: fmt.Sprintf("run_as identity %q may not be able to read source-linked files", identity), + Detail: "Source-linked deployments read files from the source workspace folder. Grant the run_as identity CAN_READ access to that folder; top-level bundle permissions do not manage source-linked files.", + Locations: []diag.Location{b.Config.GetLocation("run_as")}, + }} + } + if libraries.IsWorkspaceSharedPath(b.Config.Workspace.FilePath) { + return diag.Diagnostics{{ + Severity: diag.Warning, + Summary: fmt.Sprintf("run_as identity %q may not be able to read files in /Workspace/Shared", identity), + Detail: fmt.Sprintf("Check that the run_as identity has CAN_READ access to %s. Top-level bundle permissions do not manage files in /Workspace/Shared.", b.Config.Workspace.FilePath), + Locations: []diag.Location{b.Config.GetLocation("run_as")}, + }} + } + + runAsPermission := resources.Permission{ + UserName: runAs.UserName, + ServicePrincipalName: runAs.ServicePrincipalName, + GroupName: runAs.GroupName, + } + for _, permission := range b.Config.Permissions { + if samePrincipal(permission, runAsPermission) { + return nil + } + } + + return diag.Diagnostics{{ + Severity: diag.Warning, + Summary: fmt.Sprintf("run_as identity %q is not in bundle top-level permissions", identity), + Detail: fmt.Sprintf("Add a top-level permission so this identity can access deployed files and resources:\n"+ + " permissions:\n"+ + " - %s: %s\n"+ + " level: CAN_VIEW", identityType, identity), + Locations: []diag.Location{b.Config.GetLocation("run_as")}, }} } diff --git a/bundle/permissions/permission_diagnostics_test.go b/bundle/permissions/permission_diagnostics_test.go index e5a9214f437..f4c6fdb3984 100644 --- a/bundle/permissions/permission_diagnostics_test.go +++ b/bundle/permissions/permission_diagnostics_test.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/bundle/permissions" "github.com/databricks/cli/libs/diag" "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/require" ) @@ -48,6 +49,165 @@ func TestPermissionDiagnosticsApplyFail(t *testing.T) { require.Contains(t, diags[0].Summary, expectedMsg) } +func TestPermissionDiagnosticsRunAsAccess(t *testing.T) { + deployerPermission := resources.Permission{Level: "CAN_MANAGE", UserName: "testuser@databricks.com"} + servicePrincipalName := "aaaaaaaa-bbbb-4ccc-dddd-eeeeeeeeeeee" + servicePrincipal := jobs.JobRunAs{ServicePrincipalName: servicePrincipalName} + + testCases := []struct { + name string + runAs jobs.JobRunAs + permissions []resources.Permission + currentUser string + syncMode config.SyncMode + filePath string + wantSummary string + wantSuggestion string + }{ + { + name: "service principal without permissions", + runAs: servicePrincipal, + wantSummary: "is not in bundle top-level permissions", + wantSuggestion: "service_principal_name: " + servicePrincipalName + "\n level: CAN_VIEW", + }, + { + name: "user without permissions", + runAs: jobs.JobRunAs{UserName: "runner@example.test"}, + wantSummary: "is not in bundle top-level permissions", + wantSuggestion: "user_name: runner@example.test\n level: CAN_VIEW", + }, + { + name: "group without permissions", + runAs: jobs.JobRunAs{GroupName: "runners"}, + wantSummary: "is not in bundle top-level permissions", + wantSuggestion: "group_name: runners\n level: CAN_VIEW", + }, + { + name: "unrelated group permission", + runAs: servicePrincipal, + permissions: []resources.Permission{deployerPermission, {Level: "CAN_VIEW", GroupName: "runners"}}, + wantSummary: "is not in bundle top-level permissions", + wantSuggestion: "service_principal_name: " + servicePrincipalName, + }, + { + name: "service principal with view permission", + runAs: servicePrincipal, + permissions: []resources.Permission{deployerPermission, {Level: "CAN_VIEW", ServicePrincipalName: servicePrincipalName}}, + }, + { + name: "service principal with run permission", + runAs: servicePrincipal, + permissions: []resources.Permission{deployerPermission, {Level: "CAN_RUN", ServicePrincipalName: servicePrincipalName}}, + }, + { + name: "service principal with manage permission", + runAs: servicePrincipal, + permissions: []resources.Permission{deployerPermission, {Level: "CAN_MANAGE", ServicePrincipalName: servicePrincipalName}}, + }, + { + name: "group with view permission", + runAs: jobs.JobRunAs{GroupName: "runners"}, + permissions: []resources.Permission{deployerPermission, {Level: "CAN_VIEW", GroupName: "runners"}}, + }, + { + name: "run as deployment user", + runAs: jobs.JobRunAs{UserName: "testuser@databricks.com"}, + }, + { + name: "run as deployment service principal", + runAs: servicePrincipal, + currentUser: servicePrincipal.ServicePrincipalName, + }, + { + name: "source-linked files", + runAs: servicePrincipal, + syncMode: config.SyncModeInPlace, + wantSummary: "may not be able to read source-linked files", + wantSuggestion: "CAN_READ access to that folder; top-level bundle permissions do not manage source-linked files", + }, + { + name: "source-linked files with top-level permissions", + runAs: servicePrincipal, + permissions: []resources.Permission{deployerPermission, {Level: "CAN_VIEW", ServicePrincipalName: servicePrincipalName}}, + syncMode: config.SyncModeInPlace, + wantSummary: "may not be able to read source-linked files", + wantSuggestion: "CAN_READ access to that folder", + }, + { + name: "immutable bundle without permissions", + runAs: servicePrincipal, + syncMode: config.SyncModeImmutable, + wantSummary: "is not in bundle top-level permissions", + wantSuggestion: "level: CAN_VIEW", + }, + { + name: "files under Shared", + runAs: servicePrincipal, + filePath: "/Workspace/Shared/team/files", + wantSummary: "may not be able to read files in /Workspace/Shared", + wantSuggestion: "Check that the run_as identity has CAN_READ access to /Workspace/Shared/team/files. Top-level bundle permissions do not manage files in /Workspace/Shared.", + }, + { + name: "files under Shared with top-level permissions", + runAs: servicePrincipal, + permissions: []resources.Permission{deployerPermission, {Level: "CAN_VIEW", ServicePrincipalName: servicePrincipalName}}, + filePath: "/Workspace/Shared/team/files", + wantSummary: "may not be able to read files in /Workspace/Shared", + wantSuggestion: "Top-level bundle permissions do not manage files in /Workspace/Shared.", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + deployment := mockBundle(testCase.permissions) + deployment.Config.RunAs = &testCase.runAs + deployment.Config.Workspace.SyncMode = testCase.syncMode + deployment.Config.Workspace.FilePath = testCase.filePath + if testCase.currentUser != "" { + deployment.Config.Workspace.CurrentUser.UserName = testCase.currentUser + } + + diags := bundle.Apply(t.Context(), deployment, permissions.PermissionDiagnostics()) + if testCase.wantSummary == "" { + require.Empty(t, diags) + return + } + + require.Len(t, diags, 1) + require.Equal(t, diag.Warning, diags[0].Severity) + require.Contains(t, diags[0].Summary, testCase.wantSummary) + require.Contains(t, diags[0].Detail, testCase.wantSuggestion) + require.NoError(t, diags.Error()) + }) + } +} + +func TestPermissionDiagnosticsResourcePermissionDoesNotCoverRunAsFiles(t *testing.T) { + servicePrincipalName := "aaaaaaaa-bbbb-4ccc-dddd-eeeeeeeeeeee" + deployment := mockBundle(nil) + deployment.Config.RunAs = &jobs.JobRunAs{ServicePrincipalName: servicePrincipalName} + deployment.Config.Resources.Jobs = map[string]*resources.Job{ + "job": { + Permissions: []resources.JobPermission{{Level: "CAN_VIEW", ServicePrincipalName: servicePrincipalName}}, + }, + } + + diags := bundle.Apply(t.Context(), deployment, permissions.PermissionDiagnostics()) + require.Len(t, diags, 1) + require.Equal(t, diag.Warning, diags[0].Severity) + require.Contains(t, diags[0].Detail, "level: CAN_VIEW") +} + +func TestPermissionDiagnosticsRunAsAndDeployerWarnings(t *testing.T) { + deployment := mockBundle([]resources.Permission{{Level: "CAN_VIEW", GroupName: "other_group"}}) + deployment.Config.RunAs = &jobs.JobRunAs{UserName: "runner@example.test"} + + diags := bundle.Apply(t.Context(), deployment, permissions.PermissionDiagnostics()) + require.Len(t, diags, 2) + require.Equal(t, diag.Warning, diags[0].Severity) + require.Equal(t, diag.Recommendation, diags[1].Severity) +} + func mockBundle(permissions []resources.Permission) *bundle.Bundle { return &bundle.Bundle{ Config: config.Root{