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
19 changes: 18 additions & 1 deletion engines/terraform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type PlatformSpec struct {
TopicBlueprints map[string]*ResourceBlueprint `json:"topics,omitempty" yaml:"topics,omitempty"`
DatabaseBlueprints map[string]*ResourceBlueprint `json:"databases,omitempty" yaml:"databases,omitempty"`
EntrypointBlueprints map[string]*ResourceBlueprint `json:"entrypoints" yaml:"entrypoints"`
InfraSpecs map[string]*ResourceBlueprint `json:"infra" yaml:"infra"`
InfraSpecs map[string]*ResourceBlueprint `json:"infra" yaml:"infra"`

libraryReplacements map[libraryID]libraryVersion
}
Expand Down Expand Up @@ -371,6 +371,7 @@ type ResourceBlueprint struct {
Properties map[string]interface{} `json:"properties" yaml:"properties"`
DependsOn []string `json:"depends_on" yaml:"depends_on,omitempty"`
Variables map[string]Variable `json:"variables" yaml:"variables,omitempty"`
UsableBy []string `json:"usable_by,omitempty" yaml:"usable_by,omitempty"`
}

func (r *ResourceBlueprint) ResolvePlugin(platform *PlatformSpec) (*Plugin, error) {
Expand All @@ -393,6 +394,22 @@ func (r *ResourceBlueprint) ResolvePlugin(platform *PlatformSpec) (*Plugin, erro
return &Plugin{Library: *lib, Name: r.Source.Plugin}, nil
}

func (r *ResourceBlueprint) ValidateServiceAccess(serviceSubtype, resourceName, resourceType string) error {
// All service types are allowed if useable_by is empty (backward compatible)
if len(r.UsableBy) == 0 {
return nil
}

if !slices.Contains(r.UsableBy, serviceSubtype) {
return fmt.Errorf(
"%s '%s' cannot be accessed by service subtype '%s': this %s blueprint is only usable by service types: %v",
resourceType, resourceName, serviceSubtype, resourceType, r.UsableBy,
)
}

return nil
}

type IdentitiesBlueprint struct {
Identities []ResourceBlueprint `json:"identities" yaml:"identities"`
}
Expand Down
98 changes: 98 additions & 0 deletions engines/terraform/platform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package terraform

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestResourceBlueprint_ValidateServiceAccess(t *testing.T) {
tests := []struct {
name string
usableBy []string
serviceSubtype string
resourceName string
resourceType string
expectError bool
errorContains string
}{
{
name: "empty usable_by allows all service types",
usableBy: []string{},
serviceSubtype: "lambda",
resourceName: "my_bucket",
resourceType: "bucket",
expectError: false,
},
{
name: "nil usable_by allows all service types",
usableBy: nil,
serviceSubtype: "fargate",
resourceName: "my_database",
resourceType: "database",
expectError: false,
},
{
name: "service subtype in allowed list succeeds",
usableBy: []string{"web", "worker", "api"},
serviceSubtype: "web",
resourceName: "users_db",
resourceType: "database",
expectError: false,
},
{
name: "service subtype not in allowed list fails",
usableBy: []string{"web", "worker"},
serviceSubtype: "lambda",
resourceName: "users_db",
resourceType: "database",
expectError: true,
errorContains: "database 'users_db' cannot be accessed by service subtype 'lambda'",
},
{
name: "single allowed service type succeeds",
usableBy: []string{"fargate"},
serviceSubtype: "fargate",
resourceName: "assets",
resourceType: "bucket",
expectError: false,
},
{
name: "single allowed service type with different subtype fails",
usableBy: []string{"fargate"},
serviceSubtype: "lambda",
resourceName: "assets",
resourceType: "bucket",
expectError: true,
errorContains: "bucket 'assets' cannot be accessed by service subtype 'lambda'",
},
{
name: "error message includes allowed types list",
usableBy: []string{"web", "worker", "cron"},
serviceSubtype: "lambda",
resourceName: "cache_db",
resourceType: "database",
expectError: true,
errorContains: "only usable by service types: [web worker cron]",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
blueprint := &ResourceBlueprint{
UsableBy: tt.usableBy,
}

err := blueprint.ValidateServiceAccess(tt.serviceSubtype, tt.resourceName, tt.resourceType)

if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
}
})
}
}
18 changes: 18 additions & 0 deletions engines/terraform/resource_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ func (td *TerraformDeployment) processBucketResources(appSpec *app_spec_schema.A
return nil, fmt.Errorf("could not give access to bucket %s: service %s not found", intentName, serviceName)
}

// Validate that this service subtype is allowed to access the bucket
serviceIntent, ok := appSpec.ServiceIntents[serviceName]
if !ok {
return nil, fmt.Errorf("could not validate access to bucket %s: service %s not found in application spec", intentName, serviceName)
}
if err := spec.ValidateServiceAccess(serviceIntent.GetSubType(), intentName, "bucket"); err != nil {
return nil, err
}

servicesInput[serviceName] = map[string]interface{}{
"actions": jsii.Strings(expandedActions...),
"identities": idMap,
Expand Down Expand Up @@ -237,6 +246,15 @@ func (td *TerraformDeployment) processDatabaseResources(appSpec *app_spec_schema
return nil, fmt.Errorf("could not give access to database %s: service %s not found", intentName, serviceName)
}

// Validate that this service subtype is allowed to access the database
serviceIntent, ok := appSpec.ServiceIntents[serviceName]
if !ok {
return nil, fmt.Errorf("could not validate access to database %s: service %s not found in application spec", intentName, serviceName)
}
if err := spec.ValidateServiceAccess(serviceIntent.GetSubType(), intentName, "database"); err != nil {
return nil, err
}

servicesInput[serviceName] = map[string]interface{}{
"actions": jsii.Strings(expandedActions...),
"identities": idMap,
Expand Down