terraform-provider-minio icon indicating copy to clipboard operation
terraform-provider-minio copied to clipboard

[IAM-Policy] will be updated in-place on every re-run

Open Moep90 opened this issue 3 years ago • 5 comments

Prerequisites

  • Be sure that theres no open issue already.
    • Did not found a matching one.

Description

When applying IAM policies, I always get a change with every run.

Steps to Reproduce

  1. terraform apply
  2. terraform apply

Expected behavior

That the policies are applied and that a re-run of terraform does not cause a "update-in-place" every time.

Actual behavior

On every re-run of the same policy, terraform tells me that its changed and needs to be "updated-in-place"

Reproduces how often

100%

Versions

terraform --versions
Terraform v1.3.2
on linux_amd64
+ provider registry.terraform.io/aminueza/minio v1.6.0
+ provider registry.terraform.io/hashicorp/random v3.4.3
+ provider registry.terraform.io/hashicorp/time v0.8.0
+ provider registry.terraform.io/hashicorp/vault v3.9.1

Additional Information

# module call
module "loki" {
  source = "./modules/terraform-module-minio-s3"

  bucket = "my-bucket"

  users = {
    "loki" = {
      s3_path = "/*"
      s3_actions = [
        "s3:GetObject",
        "s3:List*",
        "s3:PutObject",
        "s3:DeleteObject"
      ]
    },
  }
}

# inside the module
resource "minio_iam_policy" "this" {

  for_each = var.create ? var.users : tomap({})
  
  name = each.key

  policy = <<-EOT
  {
      "Version": "2012-10-17",
      "Statement":  ${jsonencode([for user, user_value in var.users : {
          "Effect" = "Allow",
          "Action" = "${user_value.s3_actions}",
          "Resource" = [
            "arn:aws:s3:::${minio_s3_bucket.this.id}"
          ]
}])}
  }
EOT
depends_on = [
  minio_s3_bucket.this
]
}

Moep90 avatar Oct 10 '22 04:10 Moep90

@Moep90 Could you try updating to the latest 1.8.0 or at least 1.7.1? In principle, this should have been resolved by #180 in 1.5.0... I'm sure it's still that by using jsonencode the ordering of the fields change and thus lead to in place updates.

I couldn't reproduce it with a simpler example not using modules:

  • Terraform version: 1.3.3
  • Provider version: 1.8.0
resource "minio_iam_user" "test6" {
  name   = "test-user-jo6"
}
resource "minio_s3_bucket" "bucket6" {
  bucket = "state-terraform-s3"
  acl    = "public"
}
resource "minio_iam_policy" "bucket6" {
  name   = "test-user-jo6"
  policy = <<-EOT
  {
      "Version": "2012-10-17",
      "Statement":  ${jsonencode([{
          "Effect" = "Allow",
          "Action" = [
            "s3:GetObject",
            "s3:List*",
            "s3:PutObject",
            "s3:DeleteObject"
          ],
          "Resource" = [
            "arn:aws:s3:::${minio_s3_bucket.bucket6.id}"
          ]
        }]
    )}
  }
EOT
  depends_on = [
    minio_s3_bucket.bucket6
  ]
}

BuJo avatar Oct 25 '22 08:10 BuJo

I am seeing the same issue, using version 1.9.1 of this module on terraform 1.3.5.

resource "minio_s3_bucket" "outline_bucket" {
  bucket = "outlinetest"
  acl    = "private"
}

resource "minio_iam_user" "outline_user" {
  name          = var.outline_bucket_user
  force_destroy = true
}

data "minio_iam_policy_document" "outline_user_policy_document" {
  statement {
    sid = ""
    actions = [
      "s3:ListBucket",
      "s3:PutObject",
      "s3:GetObject",
      "s3:DeleteObject"
    ]
    effect = "Allow"
    resources = [
      "arn:aws:s3:::${minio_s3_bucket.outline_bucket.bucket}/*",
      "arn:aws:s3:::${minio_s3_bucket.outline_bucket.bucket}"
    ]
  }
}

resource "minio_iam_policy" "outline_iam_policy" {
  name      = minio_s3_bucket.outline_bucket.bucket
  policy    = data.minio_iam_policy_document.outline_user_policy_document.json
}

resource "minio_iam_user_policy_attachment" "outline_user_policy_attachment" {
  user_name   = minio_iam_user.outline_user.id
  policy_name = minio_iam_policy.outline_iam_policy.id
}

data "minio_iam_policy_document" "outline_bucket_policy_document" {
  statement {
    sid = ""
    actions = ["s3:GetBucketLocation"]
    effect = "Allow"
    principal = "*"
    resources = ["arn:aws:s3:::${minio_s3_bucket.outline_bucket.bucket}"]
  }

  statement {
    sid = ""
    actions = ["s3:ListBucket"]
    condition {
      test     = "StringEquals"
      variable = "s3:prefix"
      values = [
        "avatars",
        "public",
      ]
    }
    effect = "Allow"
    principal = "*"
    resources = ["arn:aws:s3:::${minio_s3_bucket.outline_bucket.bucket}"]
  }

  statement {
    sid = ""
    actions = ["s3:GetObject"]
    effect = "Allow"
    principal = "*"
    resources = [
      "arn:aws:s3:::${minio_s3_bucket.outline_bucket.bucket}/public*",
      "arn:aws:s3:::${minio_s3_bucket.outline_bucket.bucket}/avatars*"
    ]
  }
}

resource "minio_s3_bucket_policy" "outline_bucket_policy" {
  bucket = minio_s3_bucket.outline_bucket.bucket
  policy = data.minio_iam_policy_document.outline_bucket_policy_document.json
}

No matter how much I apply this with no changes, the plan output is always seeing changes:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # module.minio.minio_s3_bucket_policy.outline_bucket_policy will be updated in-place
  ~ resource "minio_s3_bucket_policy" "outline_bucket_policy" {
        id     = "outlinetest"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Action    = [
                          - "s3:GetBucketLocation",
                        ] -> "s3:GetBucketLocation"
                      ~ Principal = {
                          - AWS = [
                              - "*",
                            ]
                        } -> "*"
                      ~ Resource  = [
                          - "arn:aws:s3:::outlinetest",
                        ] -> "arn:aws:s3:::outlinetest"
                      + Sid       = ""
                        # (1 unchanged element hidden)
                    },
                  ~ {
                      ~ Action    = [
                          - "s3:ListBucket",
                        ] -> "s3:ListBucket"
                      ~ Principal = {
                          - AWS = [
                              - "*",
                            ]
                        } -> "*"
                      ~ Resource  = [
                          - "arn:aws:s3:::outlinetest",
                        ] -> "arn:aws:s3:::outlinetest"
                      + Sid       = ""
                        # (2 unchanged elements hidden)
                    },
                  ~ {
                      ~ Action    = [
                          - "s3:GetObject",
                        ] -> "s3:GetObject"
                      ~ Principal = {
                          - AWS = [
                              - "*",
                            ]
                        } -> "*"
                      ~ Resource  = [
                          - "arn:aws:s3:::outlinetest/avatars*",
                            "arn:aws:s3:::outlinetest/public*",
                          + "arn:aws:s3:::outlinetest/avatars*",
                        ]
                      + Sid       = ""
                        # (1 unchanged element hidden)
                    },
                ]
                # (1 unchanged element hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

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

onedr0p avatar Nov 26 '22 22:11 onedr0p

From taking a quick look at this, I'm able to reproduce the issue, and it looks like it might have something to do with how we're handling principal in minio_iam_policy_document. Supplying * as the principal causes the Minio server to set the principal to {"AWS": ["*"]}, which then fails the awspolicyequivalence check added in #180 because it's structurally different.

If anything fails in the policy equivalence check, the policy is set to the JSON document returned from the server which includes the reordered fields, which is why we're seeing that pop back up.

https://github.com/aminueza/terraform-provider-minio/blob/156f048877eb7580105d4f4576591581cdd9ffac/minio/resource_minio_s3_bucket_policy.go#L71

It looks like the real problem is the principal failing in minio_iam_policy_document because the test case in #180 using jsonencode passes. There might be other issues with that but I wasn't able to reproduce them. We could change this ticket to something about handling principals in policy documents potentially, what do you think @BuJo?

pjsier avatar Dec 03 '22 14:12 pjsier

I just had the same issue with my policy. I just change the order in my terraform code to avoid having a diff on every apply. It would be good if the provider just sorted this alphabetically internally so it doesn't matter which order the user / API provides it.

data "minio_iam_policy_document" "this" {
  dynamic "statement" {
    for_each = var.buckets
    content {
      sid = statement.value
      actions = [
        "s3:PutObject",   # <- changed order in this list
        "s3:ListBucket",
        "s3:GetObject",
        "s3:DeleteObject",
      ]
      resources = [
        "arn:aws:s3:::${statement.value}/*",  # <- swapped 
        "arn:aws:s3:::${statement.value}",    # <- these two
      ]
    }
  }
}

I briefly remember that I had a similar issue with the AWS provider a couple of years ago. I think it was the aws_ecr_repository_policy resource. They also just sorted it alphabetically to fix it.

Skaronator avatar Mar 12 '24 17:03 Skaronator

I am also facing this issue. I was not able to workaround by swapping the order my policy definition, no matter the order.

jbertozzi avatar May 31 '24 09:05 jbertozzi