Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update the method by which IAM roles are applied #223

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

edaniszewski
Copy link
Contributor

@edaniszewski edaniszewski commented Jun 30, 2020

This implementation is a pretty sizable departure of the previous implementation attempt (#219) which did not work as anticipated (#222) because it seems like the documented configuration/behavior for setting via deploymentmanager does not work as expected.

This approach does offer a bit better granularity of how functions IAM policies can be defined though, which is nice.

IAM roles are applied after a function is deployed/updated.

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Compiling function "first"...
Serverless: Uploading artifacts...
Serverless: Artifacts successfully uploaded...
Serverless: Updating deployment...
Serverless: Checking deployment update progress...
..........................
Serverless: Done...
Serverless: Setting IAM policies...
Service Information
...

Note: In order to apply IAM policies to functions, the serviceaccount you are using for serverless deploys will need the cloudfunctions.functions.setIamPolicy permission. This permission is not granted by the default cloudfunctions roles so will require you to create a custom policy.

If you do not have the appropriate permissions, you should get an error message stating you lack the permission:

  Error --------------------------------------------------
 
  Error: Permission 'cloudfunctions.functions.setIamPolicy' denied on resource 'projects/project/locations/us-central1/functions/test-svc-dev-first' (or resource may not exist).
      at Gaxios._request (/Users/edaniszewski/dev/edaniszewski/serverless-google-cloudfunctions/node_modules/gaxios/src/gaxios.ts:108:15)
      at processTicksAndRejections (internal/process/task_queues.js:89:5)
      at JWT.requestAsync (/Users/edaniszewski/dev/edaniszewski/serverless-google-cloudfunctions/node_modules/google-auth-library/build/src/auth/oauth2client.js:341:18)

IAM policies can be set a number of different ways.

Globally

service:  test-svc

provider:
  name: google
  runtime: python37
  region: us-central1
  project: project-name
  credentials: ~/.gcloud/keyfile.json
  
  # allowUnauthenticated mirrors the gcloud command line --allow-unauthenticated which
  # adds the 'roles/cloudfunctions.invoker' role for the 'allUsers' member. Setting it globally
  # applies it to all configured functions.
  allowUnauthenticated: true

  # Finer-grained IAM settings. This allows you to specify any number of bindings for functions,
  # whether they are for built-in roles or custom roles.  Setting it globally here applies the bindings
  # to all configured functions.
  iam:
    bindings:
    - role: roles/cloudfunctions.invoker
      members:
      - user:[email protected]

...

Per-function

service:  test-svc

provider:
  name: google
  runtime: python37
  region: us-central1
  project: project-name
  credentials: ~/.gcloud/keyfile.json

functions:
  first:
    handler: http
    events:
      - http: path

    # Sets 'roles/cloudfunctions.invoker' for 'allUsers' for  this function
    allowUnauthenticated: true

    # Custom IAM declarations for this function.
    iam:
        bindings:
        - role: roles/cloudfunctions.invoker
          members:
          - user:[email protected]

Reference on how to define members: https://cloud.google.com/iam/docs/reference/rest/v1/Policy#Binding

IAM policies are merged between the global/function-local scope as well. This definition:

service:  test-svc

provider:
  name: google
  runtime: python37
  region: us-central1
  project: project-name
  credentials: ~/.gcloud/keyfile.json
  iam:
    bindings:
    - role: roles/cloudfunctions.invoker
      members:
      - user:[email protected]
    - role: customRole
      members:
      - allUsers

functions:
  first:
    handler: http
    events:
      - http: path
    allowUnauthenticated: true
    iam:
        bindings:
        - role: roles/cloudfunctions.admin
          members:
          - user:[email protected]
        - role: roles/cloudfunctions.invoker
          members:
          - user:[email protected]

the full set of IAM policies would get merged to

- role: roles/cloudfunctions.invoker
  members:
  - allUsers
  - user:[email protected]
  - user:[email protected]
- role: customRole
  members:
  - allUsers
- role:  roles/cloudfunctions.admin
  members:
  - user:[email protected]

There are some caveats with this approach which this PR does not address:

  • If you deploy a function with an IAM binding and later remove that IAM binding from your serverless config, the IAM binding is not removed from the function. I didn't see any API documentation (https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions) for how to remove IAM bindings, so to do this, you would need to sls remove and re-deploy.
  • If setting IAM policies fail, the deployment is not cleaned up. I think this is okay behavior. The sls deploy command will fail, but since IAM policies are applied to functions after deployment manager is setup and creates the functions, a failure to set policy will not automatically remove functions.
  • There is currently no explicit check that the configured IAM policies were applied. This could be added in the future if needed, but it seems like its already implicitly there by virtue of the setIamPolicy call failing or not.

Fixes #222

Also related:

Copy link
Contributor

@medikoo medikoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @edaniszewski Please see my comments, they're mostly style related

'use strict';

const _ = require('lodash');
const BbPromise = require('bluebird');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's stick just to native promises (note we can fully use async/await). I believe we should get rid of bluebird, as we plant do so in a Framework).

If you find it difficult with native promises, let me know, I'll try to help

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to move to using native promises, but since Im not really experienced in the area and don't have other examples to go by, I haven't been able to get it to work quite right. If you have an example somewhere of what changing from bluebird to native looks like, it'd be much appreciated!

}
this.serverless.cli.log('Setting IAM policies...');

_.forEach(policies, (value, key) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use Object.entries().forEach instead (no point to use lodash, when we can do same with native functions)

},
};

this.provider.request(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks as orphaned promise

`Unable to set IAM bindings (${value}) for "${key}": function not found for`,
` project "${this.serverless.service.provider.project}" in region "${this.options.region}".`,
].join('');
throw new Error(errorMessage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let' use ServerlessError, it'll provide more user friendly message (regular errors should be thrown only in case of programmer errors, but not operational errors)

// Collect the configured IAM bindings at the function and provider level and merge the
// members for each defined role. This transforms the array of IAM bindings into a mapping
// in order to easily merge.
const iamBindings = _.reduce(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use antive reduce

// members for each defined role. This transforms the array of IAM bindings into a mapping
// in order to easily merge.
const iamBindings = _.reduce(
_.concat(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use native concat

@edaniszewski
Copy link
Contributor Author

I've updated the PR with some of your recommendations. I'm not sure I've handled the orphaned promises correctly, so I may need further guidance there.

I also didn't move towards using native promises instead of bluebird since it feels like transitioning from bluebird to native is a bit out-of-scope for this PR and feels to me like that project-wide change should be in its own PR

@medikoo
Copy link
Contributor

medikoo commented Jul 2, 2020

it feels like transitioning from bluebird to native is a bit out-of-scope for this PR

Yes definitely, but to avoid increase of technical debt we should not introduce any new code on Bluebird (it's not a problem to use native promises together with Bluebird). Any new code should be constructed with async/await and native promises.

@edaniszewski
Copy link
Contributor Author

@medikoo - sorry for the delay here and being a bother, but do you know of any examples (e.g. in the serverless repo or elsewhere) of migrating from bluebird to native promises? I'm a bit new to JS promises, and even though I feel kinda close to getting it right, I'm hoping there is an example for me to use as a reference and validate against.

@KavinJey
Copy link

KavinJey commented Sep 4, 2020

Wondering what needs to be done for this merge? New to OSS but would love IAM capabilities with GCP Functions

@mrlucasrib
Copy link

@medikoo Please approve this fix

@cgossain
Copy link

Just throwing in my two cents, would love for the PR to be merged as I'm looking for a way to "allowUnauthenticated" on deploy as opposed to having to manually add the permission per function in google cloud console.

@medikoo
Copy link
Contributor

medikoo commented Nov 20, 2020

@KavinJey @mrlucasrib @cgossain Do you think you can finalize this PR? Aside of suggestions pointed in review process it needs now updating with master

@ansjin
Copy link

ansjin commented Mar 8, 2021

Any plans for merging this PR soon? I am looking for the "allowUnauthenticated" option.

@HobbyProjects
Copy link

Any status update on this? This is certainly a very useful PR and a very much needed one.

@Ashar2shahid
Copy link

Plans on getting this merged?

@cgossain
Copy link

cgossain commented Apr 3, 2021

I too would be interested in this functionality. I had to write a script just to get around this but it’s an extra thing to manage.

@medikoo
Copy link
Contributor

medikoo commented Apr 6, 2021

This PR is not finalized. Can you can help with finalizing it?

@actuchicks
Copy link

It would be great if this fix could be merged soon. We're using deployment manager to manage all our cloud functions and currently have to manually assign permissions to public HTTP functions after deploy to work around this bug.

Copy link

@J-Rojas J-Rojas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edaniszewski I have provided examples as to how you can use native promises instead of bluebird. Please apply these changes and resolve any merge conflicts. It would great to get this merged.

}
});

return BbPromise.all(promises);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return can be rewritten as
return Promise.all(promises)


module.exports = {
setIamPolicy() {
return BbPromise.bind(this).then(this.getFunctions).then(this.setPolicies);
Copy link

@J-Rojas J-Rojas May 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be rewritten as:
return Promise.resolve().then(() => this.getFunctions()).then(() => this.setPolicies())

// If there are no IAM policies configured with any function, there is nothing to
// do here.
if (!policies || !Object.keys(policies).length) {
return BbPromise.resolve();
Copy link

@J-Rojas J-Rojas May 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be rewritten as
return Promise.resolve()

@J-Rojas
Copy link

J-Rojas commented May 12, 2021

In the meanwhile... this is a decent workaround, although you'll have to install another plugin.

#205 (comment)

* removes the previous implementation using deploymentmanager, as it does not appear to work as documented
* adds support for setting IAM via cloudfunctions API
* adds test cases
@edaniszewski
Copy link
Contributor Author

@J-Rojas thanks for the tips on how to update. Simple stuff, but since I'm not really a JS developer I was just unfamiliar.

Hopefully with these updates, this should now be ready for review + merge.

@J-Rojas
Copy link

J-Rojas commented May 12, 2021

@edaniszewski not a problem, I'm glad I can help and thanks for taking the time to fix this.

@J-Rojas
Copy link

J-Rojas commented May 12, 2021

@medikoo bump... can we get this merged since your requested changes have been addressed?

@Tom5om
Copy link

Tom5om commented Sep 23, 2021

I really need this too!

@aaron5670
Copy link

Status of this MR?

@cgossain
Copy link

cgossain commented Oct 2, 2022

+1 on this PR; would be very useful!

@and3rson
Copy link

and3rson commented Oct 9, 2022

Any updates on this? This is really blocking us from moving to GCP from AWS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Setting IAM policy causes deploy failure