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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reject secret scope permissions that name no principal, instead of failing after the scope is created.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
bundle:
name: test-bundle

resources:
secret_scopes:
no_principal:
name: test-scope-no-principal
permissions:
# Missing principal: rejected.
- level: READ
empty_principal:
name: test-scope-empty-principal
permissions:
# Empty principal: rejected.
- level: READ
group_name: ""
wrong_type_principal:
name: test-scope-wrong-type-principal
permissions:
# Wrong-typed principal: normalization drops it, so treated as missing.
- level: READ
group_name: []
valid:
name: test-scope-valid
permissions:
- level: READ
group_name: users

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

>>> [CLI] bundle validate
Warning: expected string, found sequence
at resources.secret_scopes.wrong_type_principal.permissions[0].group_name
in databricks.yml:22:23

Error: secret scope permission principal is required
at resources.secret_scopes.empty_principal.permissions[0]
in databricks.yml:12:7

Set one of user_name, group_name or service_principal_name

Error: secret scope permission principal is required
at resources.secret_scopes.wrong_type_principal.permissions[0]
in databricks.yml:18:7

Set one of user_name, group_name or service_principal_name

Error: secret scope permission principal is required
at resources.secret_scopes.no_principal.permissions[0]
in databricks.yml:7:7

Set one of user_name, group_name or service_principal_name

Name: test-bundle
Target: default
Workspace:
User: [USERNAME]
Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default

Found 3 errors and 1 warning

>>> [CLI] bundle deploy
Warning: expected string, found sequence
at resources.secret_scopes.wrong_type_principal.permissions[0].group_name
in databricks.yml:22:23

Error: secret scope permission principal is required
at resources.secret_scopes.empty_principal.permissions[0]
in databricks.yml:12:7

Set one of user_name, group_name or service_principal_name

Error: secret scope permission principal is required
at resources.secret_scopes.wrong_type_principal.permissions[0]
in databricks.yml:18:7

Set one of user_name, group_name or service_principal_name

Error: secret scope permission principal is required
at resources.secret_scopes.no_principal.permissions[0]
in databricks.yml:7:7

Set one of user_name, group_name or service_principal_name


>>> print_requests.py //secrets
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Without the fix, validate only warns; deploy fails later in SecretScopeFixups (direct)
# or leaves empty-principal ACLs that destroy cannot remove (terraform).
musterr trace $CLI bundle validate

# Deploy must abort at validation: no scope is created, so no partial deploy.
musterr trace $CLI bundle deploy
trace print_requests.py //secrets

rm -f out.requests.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RecordRequests = true
38 changes: 36 additions & 2 deletions bundle/config/validate/required.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,13 @@ func sortDiagnostics(diags diag.Diagnostics) {
return n
}

// Finally sort by locations as a tie breaker if summaries are the same.
return cmp.Compare(fmt.Sprintf("%v", a.Locations), fmt.Sprintf("%v", b.Locations))
// Then sort by locations as a tie breaker if summaries are the same.
if n := cmp.Compare(fmt.Sprintf("%v", a.Locations), fmt.Sprintf("%v", b.Locations)); n != 0 {
return n
}

// Sibling entries can share a location; fall back to path for a stable order.
return cmp.Compare(fmt.Sprintf("%v", a.Paths), fmt.Sprintf("%v", b.Paths))
})
}

Expand Down Expand Up @@ -184,6 +189,34 @@ func errorForInvalidGrants(ctx context.Context, b *bundle.Bundle) diag.Diagnosti
return diags
}

// errorForInvalidSecretScopePermissions errors when a permission names no principal.
// Wrong-typed values are already empty here (normalization only warns and drops them).
func errorForInvalidSecretScopePermissions(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
diags := diag.Diagnostics{}

for key, scope := range b.Config.Resources.SecretScopes {
for i, perm := range scope.Permissions {
if perm.UserName != "" || perm.GroupName != "" || perm.ServicePrincipalName != "" {
continue
}
path := fmt.Sprintf("resources.secret_scopes.%s.permissions[%d]", key, i)
// ApplyBundlePermissions rebuilds permissions via convert.FromTyped and drops
// per-entry locations, so point at the scope.
diags = diags.Append(diag.Diagnostic{
Severity: diag.Error,
Summary: "secret scope permission principal is required",
Detail: "Set one of user_name, group_name or service_principal_name",
Locations: b.Config.GetLocations("resources.secret_scopes." + key),
Paths: []dyn.Path{dyn.MustPathFromString(path)},
})
}
}

sortDiagnostics(diags)

return diags
}

// isMissingOrEmptyString reports whether v is unset, null, or an empty string.
func isMissingOrEmptyString(v dyn.Value) bool {
switch v.Kind() {
Expand Down Expand Up @@ -211,6 +244,7 @@ func isMissingOrEmptySequence(v dyn.Value) bool {
func (f *required) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
diags := errorForMissingFields(ctx, b)
diags = diags.Extend(errorForInvalidGrants(ctx, b))
diags = diags.Extend(errorForInvalidSecretScopePermissions(ctx, b))
if diags.HasError() {
return diags
}
Expand Down
Loading