hashicorp / terraform-provider-aws

The AWS Provider enables Terraform to manage AWS resources.
https://registry.terraform.io/providers/hashicorp/aws
Mozilla Public License 2.0
9.81k stars 9.16k forks source link

aws_guardduty_member attempts to resend invite when RelationshipStatus is Resigned #8206

Open lorengordon opened 5 years ago

lorengordon commented 5 years ago

Community Note

Terraform Version

Affected Resource(s)

Terraform Configuration Files

resource "aws_guardduty_detector" "member" {
  enable = true
}

resource "aws_guardduty_member" "invite" {
  provider   = "aws.master"
  depends_on = ["aws_guardduty_detector.member"]

  account_id                 = "${data.aws_caller_identity.member.account_id}"
  detector_id                = "${var.guardduty_master_detector_id}"
  email                      = "${var.email_address}"
  invite                     = true
  invitation_message         = "You are invited to enable Amazon Guardduty."
  disable_email_notification = true
}

resource "aws_guardduty_invite_accepter" "this" {
  detector_id = "${aws_guardduty_detector.member.id}"
  master_account_id = "${data.aws_caller_identity.master.account_id}"

  depends_on = ["aws_guardduty_member.invite"]
}

Expected Behavior

Before the aws provider added support for accepting a guard duty invite, we started using the CFN resource to accept the invite. Now, we are changing over to use the Terraform resource. I used a targeted destroy to delete the CFN stack, which also "unaccepted" the guard duty invite (as expected). This changed the RelationshipStatus to Resigned in the master account.

I then expected terraform apply would re-accept the invite, using the new aws_guardduty_invite_accepter resource.

Actual Behavior

Terraform detected that the invite attribute was false and attempted to resend the invite. However, this is not necessary when the status is Resigned. The member account can simply re-accept and it works.

Terraform will perform the following actions:

  + module.guardduty_member.aws_guardduty_invite_accepter.this
      id:                <computed>
      detector_id:       "SNIPPED"
      master_account_id: "701759196663"

  ~ module.guardduty_member.aws_guardduty_member.invite
      invite:            "false" => "true"

Plan: 1 to add, 1 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes
module.guardduty_member.aws_guardduty_member.invite: Modifying... (ID: SNIPPED:SNIPPED)
  invite: "false" => "true"

Error: Error applying plan:

1 error(s) occurred:

* module.guardduty_member.aws_guardduty_member.invite: 1 error(s) occurred:

* aws_guardduty_member.invite: error inviting GuardDuty Member "SNIPPED:SNIPPED": The request is rejected because the current account has already invited or is already the GuardDuty master of the given member account ID.

Steps to Reproduce

  1. terraform apply

Workaround

I was able to workaround the problem by using ignore_changes on the invite attribute:

  lifecycle {
    ignore_changes = [
      "invite",
    ]
  }
IskanderNovena commented 3 years ago

I still see this issue in Terraform v1.0.4, with provider v3.54.0.

In my case I haven't accepted an invite through other means, but I'm trying out code, and enabling and disabling GuardDuty in an organisation. When adding the member with invite = false the member gets added to the account and is active. When the invite-accepter is create, it fails because there is no invite, because the invitation has been automagically accepted when creating the aws_guardduty_member resource.

My code:

resource "aws_guardduty_detector" "member" {
  provider = aws.root
  depends_on = [
    aws_guardduty_organization_admin_account.audit
  ]
}

resource "aws_guardduty_member" "member" {
  provider                   = aws
  account_id                 = data.terraform_remote_state.root.outputs.master_account.id
  detector_id                = aws_guardduty_detector.audit.id
  email                      = data.terraform_remote_state.root.outputs.master_account.email
  invite                     = false
  disable_email_notification = false
  depends_on = [
    aws_guardduty_detector.member
  ]

  lifecycle {
    ignore_changes = [
      email,
      invite,
      disable_email_notification
    ]
  }
}

resource "aws_guardduty_invite_accepter" "member" {
  depends_on = [aws_guardduty_member.member]
  provider   = aws.root

  detector_id       = aws_guardduty_detector.member.id
  master_account_id = aws_guardduty_detector.audit.account_id
}

Resource aws_guardduty_invite_accepter.member fails on create, and as a result, the member will not be disassociated on a destroy.

The member is listed in my tfstate as follows:

$ tf console
> aws_guardduty_member.member
{
  "account_id" = "123456789012"
  "detector_id" = "9876543210987654fedcba3210987654"
  "disable_email_notification" = false
  "email" = "user@domain.tld"
  "id" = "9876543210987654fedcba3210987654:123456789012"
  "invitation_message" = tostring(null)
  "invite" = false
  "relationship_status" = "Created"
  "timeouts" = null /* object */
}

Status in the AWS Console for the member is Enabled.

Resource aws_guardduty_invite_accepter.member errors out after the default 1m create timeout with the following message:

Error: error listing GuardDuty Invitations: unable to find pending GuardDuty Invitation for detector ID (543210987654fedcba32109876543210) from master account ID (210987654321)

Setting invite to true on aws_guardduty_member.member give the same results.

IskanderNovena commented 3 years ago

Just noticed that on a subsequent apply, the resource was changed:

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # aws_guardduty_member.member has been changed
  ~ resource "aws_guardduty_member" "member" {
      - email                      = "user@domain.tld" -> null
        id                         = "9876543210987654fedcba3210987654:123456789012"
      ~ invite                     = false -> true
      ~ relationship_status        = "Created" -> "Enabled"
        # (3 unchanged attributes hidden)
    }