diff --git a/spinifex/handlers/iam/managed_policies.go b/spinifex/handlers/iam/managed_policies.go index 8e47d50bb..0eeee068c 100644 --- a/spinifex/handlers/iam/managed_policies.go +++ b/spinifex/handlers/iam/managed_policies.go @@ -282,18 +282,20 @@ var awsManagedPolicyDocs = map[string]string{ } // builtinManagedPolicyParsed is the parsed form of awsManagedPolicyDocs, built -// once at init. A malformed builtin document is a programming error and panics. -var builtinManagedPolicyParsed = func() map[string]PolicyDocument { - parsed := make(map[string]PolicyDocument, len(awsManagedPolicyDocs)) - for arn, raw := range awsManagedPolicyDocs { +// once at init. NewIAMServiceImpl propagates any parsing failure. +var builtinManagedPolicyParsed, builtinManagedPolicyParseErr = parseBuiltinManagedPolicies(awsManagedPolicyDocs) + +func parseBuiltinManagedPolicies(rawPolicies map[string]string) (map[string]PolicyDocument, error) { + parsed := make(map[string]PolicyDocument, len(rawPolicies)) + for arn, raw := range rawPolicies { var doc PolicyDocument if err := json.Unmarshal([]byte(raw), &doc); err != nil { - panic(fmt.Sprintf("iam: malformed builtin managed policy %s: %v", arn, err)) + return nil, fmt.Errorf("parse managed policy %s: %w", arn, err) } parsed[arn] = doc } - return parsed -}() + return parsed, nil +} // builtinManagedPolicyDoc returns the modeled grant document for an AWS-managed // policy ARN. ok is false for managed ARNs Spinifex does not model. diff --git a/spinifex/handlers/iam/managed_policies_test.go b/spinifex/handlers/iam/managed_policies_test.go index 29f18d032..2fca85ba6 100644 --- a/spinifex/handlers/iam/managed_policies_test.go +++ b/spinifex/handlers/iam/managed_policies_test.go @@ -1,6 +1,7 @@ package handlers_iam import ( + "errors" "testing" "github.com/mulgadc/bluebottle/pkg/iampolicy" @@ -22,6 +23,52 @@ var rdsInternalActions = []string{ "rds:GetDBBootstrapConfig", } +func TestParseBuiltinManagedPolicies(t *testing.T) { + tests := []struct { + name string + raw map[string]string + wantErr string + }{ + { + name: "valid document", + raw: map[string]string{ + "arn:valid": `{"Version":"2012-10-17","Statement":[]}`, + }, + }, + { + name: "malformed document", + raw: map[string]string{ + "arn:malformed": `{`, + }, + wantErr: "parse managed policy arn:malformed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parsed, err := parseBuiltinManagedPolicies(tt.raw) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + assert.Nil(t, parsed) + return + } + require.NoError(t, err) + assert.Len(t, parsed, len(tt.raw)) + }) + } +} + +func TestNewIAMServiceImpl_PropagatesBuiltinManagedPolicyParseError(t *testing.T) { + parseErr := errors.New("malformed builtin policy") + previousErr := builtinManagedPolicyParseErr + builtinManagedPolicyParseErr = parseErr + t.Cleanup(func() { builtinManagedPolicyParseErr = previousErr }) + + _, err := NewIAMServiceImpl(t.Context(), nil, make([]byte, 32), 1) + require.ErrorIs(t, err, parseErr) + assert.ErrorContains(t, err, "init builtin managed policies") +} + // A role whose permissions come entirely from an attached AWS-managed ARN has to // resolve to a grant, the way the stock EKS roles already do, rather than fail // closed on a document Spinifex never stores. diff --git a/spinifex/handlers/iam/service_impl.go b/spinifex/handlers/iam/service_impl.go index 537f12d76..1099f146a 100644 --- a/spinifex/handlers/iam/service_impl.go +++ b/spinifex/handlers/iam/service_impl.go @@ -112,6 +112,9 @@ var _ IAMService = (*IAMServiceImpl)(nil) // clusterSize sets the replication factor; pass 1 for single-node or test setups. // The context bounds bucket creation and the schema migrations only. func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byte, clusterSize int) (*IAMServiceImpl, error) { + if builtinManagedPolicyParseErr != nil { + return nil, fmt.Errorf("init builtin managed policies: %w", builtinManagedPolicyParseErr) + } if len(masterKey) != 32 { return nil, fmt.Errorf("master key must be 32 bytes, got %d", len(masterKey)) }