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

feat!: Update audit_config submodule to support multiple log types #108

Merged

Conversation

agnnn
Copy link
Contributor

@agnnn agnnn commented Aug 26, 2020

Update modules/audit_config to support multiple log types for a specified service while defining audit logs config.
Make use of dynamic block on resource deployment.

Fixes:

  1. Supports multiple log types for a service.
  2. Added README file with example usage.

Pradeep Ravindran added 4 commits August 26, 2020 09:42
Update variable type definition to support multiple log types.
Use dynamic block on the resource.
@morgante
Copy link
Contributor

@agnnn is it necessary to have each service only listed once? I'm wondering if the existing implementation would work with a config like this:

  audit_log_config = [
    {
      service    = "pubsub.googleapis.com"      
      log_config = {
          log_type = DATA_READ
          exempted_members = [
            "group:my-group@my-org.com",
            "serviceAccount:my-sa@my-project.iam.gserviceaccount.com",
            "user:my-user@my-org.com"
          ]
        },
  },
  {
      service    = "pubsub.googleapis.com" 
      log_config = {
          log_type = DATA_WRITE
          exempted_members = [
            "group:my-group@my-org.com",
            "serviceAccount:my-sa@my-project.iam.gserviceaccount.com",
            "user:my-user@my-org.com"
          ]
    }
  }]

@agnnn
Copy link
Contributor Author

agnnn commented Aug 26, 2020

@morgante So basically... each resource is authoritative and not additive. When we have log_type for a single service defined on multiple resources, it will apply only one configuration. Every time doing a terraform apply.. it goes into a loop applying changes for that particular service that got modified due to the last terraform apply.
This is the same case for the existing implementation and the new implementation.
Because of this same limitation, the proposed implementation now supports having multiple log types defined for a single service on a single resource.
So yes, we need to specify each service only once.

@morgante
Copy link
Contributor

morgante commented Aug 26, 2020

Got it, thanks.

I think we could actually accomplish this without changing the module's variable interface, it will just require some more logic in the module. The logic would be something like:

  1. Gather a list of all unique services (ex. using distinct()) that were provided in the variable.
  2. For_each on each service
  3. On each service, add the audit configs listed for that service.

I'd like to take the above approach for 2 reasons:

  1. Allowing this change to be backwards-compatible
  2. Providing a simpler (less nested) interface to module users.

@kpeder
Copy link

kpeder commented Aug 26, 2020

I'd like to take the above approach for 2 reasons:

  1. Allowing this change to be backwards-compatible
  2. Providing a simpler (less nested) interface to module users.

re: 1. - It is a breaking change, but on the other hand, the current implementation is broken.
re: 2. - Con: interface input is not DRY

/the peanut gallery :)

@morgante
Copy link
Contributor

re: 1. - It is a breaking change, but on the other hand, the current implementation is broken.

The current implementation isn't broken. It works fine for users who only want 1 consistent config—this PR is an enhancement, not a bug fix.

@kpeder
Copy link

kpeder commented Aug 26, 2020

re: 1. - It is a breaking change, but on the other hand, the current implementation is broken.

The current implementation isn't broken. It works fine for users who only want 1 consistent config—this PR is an enhancement, not a bug fix.

Arguably, and I am not arguing particularly hard here, the initial implementation assumes an additive resource vs an authoritative one. Since the underlying resource supports multiple log types per service, and since the current implementation can't configure more than one log type per service, I consider it broken for a (not so arguably, since each service supports 3 different audit configs) very common use case.

Functionally, the important part is addition of the dynamic block, and this is why I'm not going to argue too hard. I will note that the current implementation is released and can be pinned to preserve the simpler use case.

@agnnn
Copy link
Contributor Author

agnnn commented Sep 14, 2020

@morgante Any updates on this ?? Or do you still wanna go with the approach you suggested earlier ??

@morgante
Copy link
Contributor

@agnnn Yes, I'd like this to be updated to the approach I suggested (which will be backwards-compatible).

@kpeder
Copy link

kpeder commented Oct 31, 2020

So, I have done some investigation on how this could be implemented, and the conversion works with this pattern:

variable "audit_log_config" {
  type = list(object({ service : string, log_type : string, exempted_members : list(string) }))
  default = [
    {
      service          = "pubsub.googleapis.com"      
      log_type         = "DATA_READ"
      exempted_members = [
            "group:my-group@my-org.com",
            "serviceAccount:my-sa@my-project.iam.gserviceaccount.com",
            "user:my-user@my-org.com"
          ]
    },
    {
      service          = "pubsub.googleapis.com" 
      log_type         = "DATA_WRITE"
      exempted_members = [
            "group:my-group@my-org.com",
            "serviceAccount:my-sa@my-project.iam.gserviceaccount.com",
            "user:my-user@my-org.com"
          ]
    }
  ]
}

locals {
  audit_log_config = {
    for key, val in var.audit_log_config :
    val.service => val...
  }
}

output "test" {
  value = local.audit_log_config
}

...resulting in the desired input format for the new implementation:

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

test = {
  "pubsub.googleapis.com" = [
    {
      "exempted_members" = [
        "group:my-group@my-org.com",
        "serviceAccount:my-sa@my-project.iam.gserviceaccount.com",
        "user:my-user@my-org.com",
      ]
      "log_type" = "DATA_READ"
      "service" = "pubsub.googleapis.com"
    },
    {
      "exempted_members" = [
        "group:my-group@my-org.com",
        "serviceAccount:my-sa@my-project.iam.gserviceaccount.com",
        "user:my-user@my-org.com",
      ]
      "log_type" = "DATA_WRITE"
      "service" = "pubsub.googleapis.com"
    },
  ]
}

@thiagonache
Copy link

thiagonache commented Oct 31, 2020

@kpeder my understanding is that you can go ahead with your approach. Since you keep the variable the same and it still works with just one item, we are good. Feel free to tag me before asking for Morgante's final review. An additional note is, as you probably noticed, the permissions are not authoritative. It is a side effect because of the for loop overwriting the key.
I'd encourage you to run tests before pushing it to make sure it will pass.

@agnnn
Copy link
Contributor Author

agnnn commented Nov 6, 2020

@morgante Hey, so I have updated the PR based on your requested changes and it is almost ready to be reviewed except for the failing check, could you inspect the logs and suggest what is the error on lint ??

@thiagonache
Copy link

hi @agnnn let me see if I can reproduce the error.

Copy link
Member

@bharathkkb bharathkkb left a comment

Choose a reason for hiding this comment

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

Thanks for the PR @agnnn
Please run make generate_docs to regenerate docs. You can also run make docker_test_lint to test link check locally.
Errors from CI

Checking for documentation generation
diff -r '--exclude=.terraform' '--exclude=.kitchen' '--exclude=.git' '--exclude=autogen' '--exclude=*.tfvars' /workspace/modules/audit_config/README.md /tmp/tmp.K5k2UROctV/generate_docs/workspace/modules/audit_config/README.md
49,51c49,50
< | audit_log_config | List of objects to be added to audit log config | list(object({ service : string, log_type : string, exempted_members : list(string) })) | n/a | yes |
< | project | GCP Project ID | string | n/a | yes |
< 
---
> | audit\_log\_config | List of objects to be added to audit log config | object | n/a | yes |
> | project | Project to add the IAM policies/bindings | string | n/a | yes |
57,58c56
< | audit_log_config | Map of log type and exempted members added to service |
< 
---
> | audit\_log\_config | Map of log type and exempted members to be added to service |
Error: Documentation generation has not been run, please run the
'make docker_generate_docs' command and commit the above changes.
Checking for trailing whitespace
./modules/audit_config/README.md:11:  
./modules/audit_config/README.md:14:      service          = "pubsub.googleapis.com"      
./modules/audit_config/README.md:23:      service          = "storage.googleapis.com" 
./modules/audit_config/README.md:32:      service          = "pubsub.googleapis.com" 
Error: Trailing whitespace found in the lines above.

Checking for missing newline at end of file
Running shellcheck
Checking file headers
Running flake8
Running terraform fmt
Running terraform validate
terraform_validate ./examples/billing_account 
Success! The configuration is valid.

@thiagonache
Copy link

@agnnn seems to be an issue in the CI, not related to your PR. Everything passes locally on my machine. Meanwhile, I'll try to help and give it my review.

@thiagonache
Copy link

thiagonache commented Nov 6, 2020

@agnnn seems to be an issue in the CI, not related to your PR. Everything passes locally on my machine. Meanwhile, I'll try to help and give it my review.

🤦 I ran tests from the master branch. My bad.

@thiagonache
Copy link

thiagonache commented Nov 6, 2020

@bharathkkb It looks good to me (just need to run make docker_generate_docs). I deployed with the latest release and then applied this branch and the plan shows no change as expected.

(Sorry for previous mistakes. I'll do better on paying attention to the details)

@agnnn
Copy link
Contributor Author

agnnn commented Nov 6, 2020

@bharathkkb Thanks for your quick reply. So I did run make docker_generate_docs as well as make docker_test_lint and pushed the commit.

make docker_generate_docs
docker run --rm -it \
        -v /mnt/c/Users/Ravindran/Desktop/work/terraform-google-iam:/workspace \
        gcr.io/cloud-foundation-cicd/cft/developer-tools:0.12.0 \
        /bin/bash -c 'source /usr/local/bin/task_helper_functions.sh && generate_docs'
Generating markdown docs with terraform-docs
Working in ./examples/billing_account ...
Success!
Working in ./examples/custom_role_org ...
Success!
Working in ./examples/custom_role_project ...
Success!
Working in ./examples/folder ...
Success!
Working in ./examples/kms_crypto_key ...
Success!
Working in ./examples/kms_key_ring ...
Success!
Working in ./examples/member_iam ...
Success!
Working in ./examples/organization ...
Success!
Working in ./examples/project ...
Success!
Working in ./examples/project_conditions ...
Success!
Working in ./examples/pubsub_subscription ...
Success!
Working in ./examples/pubsub_topic ...
Success!
Working in ./examples/service_account ...
Success!
Working in ./examples/stackdriver_agent_roles ...
Success!
Working in ./examples/storage_bucket ...
Success!
Working in ./examples/subnet ...
Success!
**Working in ./modules/audit_config ...
Success!**
Working in ./modules/billing_accounts_iam ...
Success!
Working in ./modules/custom_role_iam ...
Success!
Working in ./modules/folders_iam ...
Success!
Skipping ./modules/helper because README.md does not exist.
Working in ./modules/kms_crypto_keys_iam ...
Success!
Working in ./modules/kms_key_rings_iam ...
Success!
Working in ./modules/member_iam ...
Success!
Working in ./modules/organizations_iam ...
Success!
Working in ./modules/projects_iam ...
Success!
Working in ./modules/pubsub_subscriptions_iam ...
Success!
Working in ./modules/pubsub_topics_iam ...
Success!
Working in ./modules/service_accounts_iam ...
Success!
Working in ./modules/storage_buckets_iam ...
Success!
Working in ./modules/subnets_iam ...
Success!
Skipping ./test/fixtures/additive because README.md does not exist.
Skipping ./test/fixtures/authoritative because README.md does not exist.
Skipping ./test/fixtures/billing-iam because README.md does not exist.
Skipping ./test/fixtures/custom-role because README.md does not exist.
Skipping ./test/fixtures/helper because README.md does not exist.
Skipping ./test/fixtures/helper/base because README.md does not exist.
Skipping ./test/fixtures/member-iam because README.md does not exist.
Skipping ./test/fixtures/static-and-dynamic because README.md does not exist.
Skipping ./test/fixtures/static-and-dynamic/static_projects because README.md does not exist.
Skipping ./test/setup because README.md does not exist.
make docker_test_lint
docker run --rm -it \
        -v /mnt/c/Users/Ravindran/Desktop/work/terraform-google-iam:/workspace \
        gcr.io/cloud-foundation-cicd/cft/developer-tools:0.12.0 \
        /usr/local/bin/test_lint.sh
Checking for documentation generation
Checking for trailing whitespace
Checking for missing newline at end of file
Running shellcheck
Checking file headers
Running flake8
Running terraform fmt
Running terraform validate
terraform_validate ./examples/billing_account
Success! The configuration is valid.

terraform_validate ./examples/custom_role_org
Success! The configuration is valid.

terraform_validate ./examples/custom_role_project
Success! The configuration is valid.

terraform_validate ./examples/folder
Success! The configuration is valid.

terraform_validate ./examples/kms_crypto_key
Success! The configuration is valid.

terraform_validate ./examples/kms_key_ring
Success! The configuration is valid.

terraform_validate ./examples/member_iam
Success! The configuration is valid.

terraform_validate ./examples/organization
Success! The configuration is valid.

terraform_validate ./examples/project
Success! The configuration is valid.

terraform_validate ./examples/project_conditions
Success! The configuration is valid.

terraform_validate ./examples/pubsub_subscription
Success! The configuration is valid.

terraform_validate ./examples/pubsub_topic
Success! The configuration is valid.

terraform_validate ./examples/service_account
Success! The configuration is valid.

terraform_validate ./examples/stackdriver_agent_roles
Success! The configuration is valid.

terraform_validate ./examples/storage_bucket
Success! The configuration is valid.

terraform_validate ./examples/subnet
Success! The configuration is valid.

**terraform_validate ./modules/audit_config
Success! The configuration is valid.**

terraform_validate ./modules/billing_accounts_iam
Success! The configuration is valid.

terraform_validate ./modules/custom_role_iam
Success! The configuration is valid.

terraform_validate ./modules/folders_iam
Success! The configuration is valid.

terraform_validate ./modules/helper
Success! The configuration is valid.

terraform_validate ./modules/kms_crypto_keys_iam
Success! The configuration is valid.

terraform_validate ./modules/kms_key_rings_iam
Success! The configuration is valid.

terraform_validate ./modules/member_iam
Success! The configuration is valid.

terraform_validate ./modules/organizations_iam
Success! The configuration is valid.

terraform_validate ./modules/projects_iam
Success! The configuration is valid.

terraform_validate ./modules/pubsub_subscriptions_iam
Success! The configuration is valid.

terraform_validate ./modules/pubsub_topics_iam
Success! The configuration is valid.

terraform_validate ./modules/service_accounts_iam
Success! The configuration is valid.

terraform_validate ./modules/storage_buckets_iam
Success! The configuration is valid.

terraform_validate ./modules/subnets_iam
Success! The configuration is valid.

terraform_validate ./test/fixtures/additive
Success! The configuration is valid.

terraform_validate ./test/fixtures/authoritative
Success! The configuration is valid.

terraform_validate ./test/fixtures/billing-iam
Success! The configuration is valid.

terraform_validate ./test/fixtures/custom-role
Success! The configuration is valid.

terraform_validate ./test/fixtures/helper
Success! The configuration is valid.

terraform_validate ./test/fixtures/helper/base
Success! The configuration is valid.

terraform_validate ./test/fixtures/member-iam
Success! The configuration is valid.

terraform_validate ./test/fixtures/static-and-dynamic
Success! The configuration is valid.

terraform_validate ./test/fixtures/static-and-dynamic/static_projects
Success! The configuration is valid.

terraform_validate ./test/setup
Success! The configuration is valid.

Everything looks good on my local system. But on the PR it is still failing the check. Any suggestions ??

Copy link
Member

@bharathkkb bharathkkb left a comment

Choose a reason for hiding this comment

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

Not sure why your local CI is passing but Checking for trailing whitespace, I have highlighted the lint nits below

modules/audit_config/README.md Outdated Show resolved Hide resolved
modules/audit_config/README.md Outdated Show resolved Hide resolved
modules/audit_config/README.md Outdated Show resolved Hide resolved
modules/audit_config/README.md Outdated Show resolved Hide resolved
Co-authored-by: Bharath KKB <bharathkrishnakb@gmail.com>
@agnnn
Copy link
Contributor Author

agnnn commented Nov 6, 2020

@bharathkkb Done, All checks are passing now. This Looks good to go for a review.

Copy link
Member

@bharathkkb bharathkkb left a comment

Choose a reason for hiding this comment

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

Overall LGTM
Since the keys are changing there will be a delete recreate which I think is okay for a minor release.
/cc @morgante

@bharathkkb bharathkkb changed the title Update audit_config submodule to support multiple log types feat: Update audit_config submodule to support multiple log types Nov 6, 2020
@thiagonache
Copy link

thiagonache commented Nov 6, 2020 via email

@bharathkkb
Copy link
Member

bharathkkb commented Nov 6, 2020

@thiagonache IIUC the change

- key => val
+ val.service => val...

will change the key from an array index like 0 to a string like pubsub.googleapis.com which cause TF to want to delete previous resource instances and recreate them. Now thinking about it, it might even need two TF apply's as the first one may error out due to TF wanting to the create and delete concurrently and each is authoritative.

@thiagonache
Copy link

@thiagonache IIUC the change

- key => val
+ val.service => val...

will change the key from an array index like 0 to a string like pubsub.googleapis.com which cause TF to want to delete previous resource instances and recreate them. Now thinking about it, it might even need two TF apply's as the first one may error out due to TF wanting to the create and delete concurrently and each is authoritative.

My understanding is since the value is variadic, the state won't change if you don't change the input. And I actually tested it out, if the input is the same there's no changes on the terraform plan.

@bharathkkb
Copy link
Member

@thiagonache I just tested it and it produced a diff with the new keys; for instance destroy google_project_iam_audit_config.project["0"] and create google_project_iam_audit_config.project["storage.googleapis.com"]. Curious why you did not observe this.

full diff: https://gist.github.com/bharathkkb/3f48d70a015c5c76ebc751259b601493
config:

{
    service          = "storage.googleapis.com"
    log_type         = "DATA_READ"
    exempted_members = ["serviceAccount:ci-iam-member-0-5da1@project-id.iam.gserviceaccount.com"]
},
{
    service          = "allServices"
    log_type         = "DATA_READ"
    exempted_members = ["serviceAccount:ci-iam-member-0-5da1@project-id.iam.gserviceaccount.com"]

  }

@thiagonache
Copy link

thiagonache commented Nov 7, 2020

@thiagonache I just tested it and it produced a diff with the new keys; for instance destroy google_project_iam_audit_config.project["0"] and create google_project_iam_audit_config.project["storage.googleapis.com"]. Curious why you did not observe this.

full diff: https://gist.github.com/bharathkkb/3f48d70a015c5c76ebc751259b601493
config:

{
    service          = "storage.googleapis.com"
    log_type         = "DATA_READ"
    exempted_members = ["serviceAccount:ci-iam-member-0-5da1@project-id.iam.gserviceaccount.com"]
},
{
    service          = "allServices"
    log_type         = "DATA_READ"
    exempted_members = ["serviceAccount:ci-iam-member-0-5da1@project-id.iam.gserviceaccount.com"]

  }

@bharathkkb Idk what I've done and how you still don't send me to hell and stop bothering you. I'm bad at reviews :( I wanna do it more to get better, I hope you don't mind.
As to apologies I've created the patch to be applied in @agnnn's branch to fix this and works as discussed in the comments. Here is the link. Pradeep, reach me out on slack if you need help to apply the patch.

@bharathkkb
Copy link
Member

@thiagonache np :) Regarding your modifications, I am still not sure why we need to keep the array indices as keys. Doing so will create unnecessary diffs if for instance I modify the order of the input list of objects?

@thiagonache
Copy link

thiagonache commented Nov 7, 2020

@bharathkkb Because I use the keys to group configs but keep the state as list by getting distinct apis and loop over it. It was suggested by Morgante

@bharathkkb
Copy link
Member

@thiagonache ack, IIUC having the local.api_services as index => service serves mostly as a way to prevent that delete recreate. IMHO we should just delete recreate and transition to using services as keys as it would still keep the interface same and added benefit of not being dependent on order of objects in audit_log_config.

@thiagonache
Copy link

thiagonache commented Nov 8, 2020 via email

@kpeder
Copy link

kpeder commented Nov 8, 2020

The real point of the update is around ensuring more than one configuration can be applied per service api.

I would suggest to update test inputs and assertions to ensure multiple log type configuration is happening properly.

IMO the destroy/recreate isn't a big issue... even on destroy its only a configuration update under the hood; no resource is being removed.

@thiagonache
Copy link

thiagonache commented Nov 8, 2020 via email

@thiagonache
Copy link

thiagonache commented Nov 8, 2020

As a module user, I wouldn't be happy with my resources being recreated in a minor release upgrade

@thiagonache
Copy link

Got it, thanks.

I think we could actually accomplish this without changing the module's variable interface, it will just require some more logic in the module. The logic would be something like:

  1. Gather a list of all unique services (ex. using distinct()) that were provided in the variable.

  2. For_each on each service

  3. On each service, add the audit configs listed for that service.

I'd like to take the above approach for 2 reasons:

  1. Allowing this change to be backwards-compatible

  2. Providing a simpler (less nested) interface to module users.

@kpeder @bharathkkb this is why I coded my patch.

@agnnn
Copy link
Contributor Author

agnnn commented Nov 9, 2020

Hey @bharathkkb from the above comments from @kpeder and @thiagonache what is your suggestion, if you're planning to do a major release, I assume it should be okay to recreate resources.

@kpeder
Copy link

kpeder commented Nov 9, 2020

Hey @bharathkkb from the above comments from @kpeder and @thiagonache what is your suggestion, if you're planning to do a major release, I assume it should be okay to recreate resources.

I would endorse Thiago's patch; and open an issue to track and add the breaking change as is here on next major release?

@bharathkkb
Copy link
Member

Historically we have done minor releases for some safe delete recreates like IAM. Ack that even a short time without audit is not ideal in prod so we can do a major release too.

@bharathkkb bharathkkb changed the title feat: Update audit_config submodule to support multiple log types feat!: Update audit_config submodule to support multiple log types Nov 9, 2020
@morgante
Copy link
Contributor

morgante commented Nov 9, 2020

I'm fine with making this a breaking change if we think the delete/recreate is a major issue (even though it's only for a tiny amount of time).

Can we add some test cases though? I'm really not sure if this implementation works the way I was looking for.

@morgante morgante merged commit 1e5d793 into terraform-google-modules:master Mar 10, 2021
@release-please release-please bot mentioned this pull request Mar 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants