aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
89 stars 79 forks source link

IAM not authorized for custom types if default auth mode is not IAM #2929

Open palpatim opened 1 month ago

palpatim commented 1 month ago

Environment information

System:
  OS: macOS 15.0.1
  CPU: (10) arm64 Apple M1 Pro
  Memory: 121.84 MB / 32.00 GB
  Shell: /bin/zsh
Binaries:
  Node: 18.20.4 - ~/.local/share/mise/installs/node/18/bin/node
  Yarn: 1.22.22 - ~/.local/share/mise/installs/node/18/bin/yarn
  npm: 10.7.0 - ~/.local/share/mise/installs/node/18/bin/npm
  pnpm: undefined - undefined
NPM Packages:
  @aws-amplify/auth-construct: 1.3.1
  @aws-amplify/backend: 1.3.0
  @aws-amplify/backend-auth: 1.2.0
  @aws-amplify/backend-cli: 1.2.8
  @aws-amplify/backend-data: 1.1.4
  @aws-amplify/backend-deployer: 1.1.4
  @aws-amplify/backend-function: 1.5.0
  @aws-amplify/backend-output-schemas: 1.2.0
  @aws-amplify/backend-output-storage: 1.1.2
  @aws-amplify/backend-secret: 1.1.3
  @aws-amplify/backend-storage: 1.2.0
  @aws-amplify/cli-core: 1.1.3
  @aws-amplify/client-config: 1.3.2
  @aws-amplify/deployed-backend-client: 1.4.1
  @aws-amplify/form-generator: 1.0.3
  @aws-amplify/model-generator: 1.0.8
  @aws-amplify/platform-core: 1.1.0
  @aws-amplify/plugin-types: 1.3.0
  @aws-amplify/sandbox: 1.2.2
  @aws-amplify/schema-generator: 1.2.4
  aws-amplify: 6.6.2
  aws-cdk: 2.160.0
  aws-cdk-lib: 2.160.0
  typescript: 5.6.2
AWS environment variables:
  AWS_PROFILE = personal
  AWS_DEFAULT_REGION = us-west-2
  AWS_SCHMELTE_ACCOUNT = 779656175277
  AWS_STS_REGIONAL_ENDPOINTS = regional
  AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  AWS_SDK_LOAD_CONFIG = 1
CDK environment variables:
  CDK_DEFAULT_ACCOUNT = 779656175277
  CDK_DEFAULT_REGION = us-west-2

Data packages

gh2837-iam-auth-custom-mutations@0.0.0 /Users/schmelte/src/data/test-apps/gen2/gh2837-iam-auth-custom-mutations
├─┬ @aws-amplify/backend-cli@1.2.8
│ └─┬ @aws-amplify/schema-generator@1.2.4
│   └── @aws-amplify/graphql-schema-generator@0.9.4
└─┬ @aws-amplify/backend@1.3.0
  └─┬ @aws-amplify/backend-data@1.1.4
    └── @aws-amplify/data-construct@1.10.1

Description

During investigation of https://github.com/aws-amplify/amplify-category-api/issues/2837, @atierian noted a case not covered by the fix. In the case of a schema with only custom operations and types, the @aws_iam annotation won't get automatically added to the custom type. E.g., in the following schema:

type Foo { description: String }
type Query { getFoo: Foo }

The transformed schema would correctly include @aws_iam on the getFoo field, but not on the Foo type itself, meaning that a properly authorized getFoo query would be unable to view the actual result.

There are a couple of notes to help gauge priority:

  1. A workaround for this bug is to add a static auth rule to the field, which triggers the auth transformer to process the field:

    type Foo {
      description: String @auth(rules: [{ allow: groups, groups: ["ZZZ_DOES_NOT_EXIST"] }])
    }
  2. This does not pertain to custom operation fields that have scalar types: the below example works fine after the fix in #2921:

    type Query {
      getFooCustom: String
    }
palpatim commented 1 month ago

Once we fix this issue, be sure to enable the skip()ped tests that cover this case.

palpatim commented 1 month ago

Note: We also need to make sure this fix properly handles the EventInvocationResponse type for .async() function handlers (see https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/#async-function-handlers). It should, since that type must be specified in the schema, but we'll want a specific test for it.

palpatim commented 1 week ago

We do plan on addressing this. In the meantime, here are a few workarounds:

  1. If you only need to access a custom operation via IAM, you can set the API's default authorization mode to iam

    export const data = defineData({
      authorizationModes: {
        defaultAuthorizationMode: "iam",
      },
      ...

    This instructs AppSync to use IAM authorization on all access by default. However, this will not work if you also need additional authorization modes on a custom operation, since explicitly adding an authorization rule to a field overrides the default authorization mode.

  2. Use scalar return type for the return type of the custom operation:

    foo: a
      .mutation()
      .arguments({
        id: a.string().required(),
      })
      // v------- NOTE the return type is a scalar, which needs no further authorization
      .returns(a.integer())
    ...
  3. Convert the return type to a model rather than a customType, and disable all operations:

    Foo: a
      .model({
        value: a.integer(),
      })
      .disableOperations(["mutations", "subscriptions", "queries"])
      .authorization((allow) => [allow.group("ZZZDOESNOTEXIST")]),
    
    ...
    
    foo: a
      .mutation()
      .arguments({
        id: a.string().required(),
      })
      // v------- NOTE the return type is the model
      .returns(a.ref('Foo'))

    This has the downside of creating an empty DynamoDB table for the return type, but by disabling operations and explicitly setting an unreachable authorization mode, it ensures that only IAM access is authorized on the type.

    This works because Amplify supports adding implicit IAM authorization to models even if you specify other explicit authorization modes.