ansible-collections / amazon.aws

Ansible Collection for Amazon AWS
GNU General Public License v3.0
309 stars 336 forks source link

aws_s3_bucket_info needs ListBuckets permission even if a bucket name is given. #2183

Open mohag opened 3 months ago

mohag commented 3 months ago

Summary

When using aws_s3_bucket_info with a supplied bucket name to determine the bucket location, an unnecessary ListBuckets call means that more permissions are needed than what is needed to determine a bucket's location.

get_bucket_list always call list_buckets, even if a bucket name is given, which fails if the IAM role / user does not have ListBuckets permissions (but has sufficient permissions to get the requested info)

(Yes, I'm likely on some unsupported versions, but the logic in the code will still have the same results on up to date versions)

Issue Type

Bug Report

Component Name

s3_bucket_info

Ansible Version

$ ansible --version
ansible [core 2.13.7]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/gertvdb/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.8/dist-packages/ansible
  ansible collection location = /home/gertvdb/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.8.10 (default, Nov 22 2023, 10:22:35) [GCC 9.4.0]
  jinja version = 3.1.2
  libyaml = True

Collection Versions

$ ansible-galaxy collection list
# /home/gertvdb/.ansible/collections/ansible_collections
Collection           Version
-------------------- -------
amazon.aws           7.5.0
ansible.posix        1.5.4
community.general    9.1.0
community.mongodb    1.7.4
community.postgresql 2.3.2
google.cloud         1.3.0
kubernetes.core      2.4.0

# /usr/local/lib/python3.8/dist-packages/ansible_collections
Collection                    Version
----------------------------- -------
amazon.aws                    3.5.0
ansible.netcommon             3.1.3
ansible.posix                 1.4.0
ansible.utils                 2.8.0
ansible.windows               1.12.0
arista.eos                    5.0.1
awx.awx                       21.10.0
azure.azcollection            1.14.0
check_point.mgmt              2.3.0
chocolatey.chocolatey         1.3.1
cisco.aci                     2.3.0
cisco.asa                     3.1.0
cisco.dnac                    6.6.1
cisco.intersight              1.0.22
cisco.ios                     3.3.2
cisco.iosxr                   3.3.1
cisco.ise                     2.5.9
cisco.meraki                  2.13.0
cisco.mso                     2.1.0
cisco.nso                     1.0.3
cisco.nxos                    3.2.0
cisco.ucs                     1.8.0
cloud.common                  2.1.2
cloudscale_ch.cloud           2.2.3
community.aws                 3.6.0
community.azure               1.1.0
community.ciscosmb            1.0.5
community.crypto              2.9.0
community.digitalocean        1.22.0
community.dns                 2.4.2
community.docker              2.7.3
community.fortios             1.0.0
community.general             5.8.3
community.google              1.0.0
community.grafana             1.5.3
community.hashi_vault         3.4.0
community.hrobot              1.6.0
community.libvirt             1.2.0
community.mongodb             1.4.2
community.mysql               3.5.1
community.network             4.0.2
community.okd                 2.2.0
community.postgresql          2.3.1
community.proxysql            1.4.0
community.rabbitmq            1.2.3
community.routeros            2.5.0
community.sap                 1.0.0
community.sap_libs            1.4.0
community.skydive             1.0.0
community.sops                1.5.0
community.vmware              2.10.2
community.windows             1.11.1
community.zabbix              1.9.0
containers.podman             1.10.1
cyberark.conjur               1.2.0
cyberark.pas                  1.0.14
dellemc.enterprise_sonic      1.1.2
dellemc.openmanage            5.5.0
dellemc.os10                  1.1.1
dellemc.os6                   1.0.7
dellemc.os9                   1.0.4
f5networks.f5_modules         1.21.0
fortinet.fortimanager         2.1.7
fortinet.fortios              2.2.1
frr.frr                       2.0.0
gluster.gluster               1.0.2
google.cloud                  1.0.2
hetzner.hcloud                1.9.0
hpe.nimble                    1.1.4
ibm.qradar                    2.1.0
ibm.spectrum_virtualize       1.10.0
infinidat.infinibox           1.3.12
infoblox.nios_modules         1.4.1
inspur.ispim                  1.2.0
inspur.sm                     2.3.0
junipernetworks.junos         3.1.0
kubernetes.core               2.3.2
lowlydba.sqlserver            1.2.0
mellanox.onyx                 1.0.0
netapp.aws                    21.7.0
netapp.azure                  21.10.0
netapp.cloudmanager           21.21.0
netapp.elementsw              21.7.0
netapp.ontap                  21.24.1
netapp.storagegrid            21.11.1
netapp.um_info                21.8.0
netapp_eseries.santricity     1.3.1
netbox.netbox                 3.9.0
ngine_io.cloudstack           2.3.0
ngine_io.exoscale             1.0.0
ngine_io.vultr                1.1.2
openstack.cloud               1.10.0
openvswitch.openvswitch       2.1.0
ovirt.ovirt                   2.4.1
purestorage.flasharray        1.15.0
purestorage.flashblade        1.10.0
purestorage.fusion            1.2.0
sensu.sensu_go                1.13.1
servicenow.servicenow         1.0.6
splunk.es                     2.1.0
t_systems_mms.icinga_director 1.31.4
theforeman.foreman            3.7.0
vmware.vmware_rest            2.2.0
vultr.cloud                   1.3.1
vyos.vyos                     3.0.1
wti.remote                    1.0.4

AWS SDK versions

$ pip show boto boto3 botocore
 pip show boto boto3 botocore
WARNING: Package(s) not found: boto
Name: boto3
Version: 1.9.253
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
Author-email: UNKNOWN
License: Apache License 2.0
Location: /usr/lib/python3/dist-packages
Requires:
Required-by:
---
Name: botocore
Version: 1.16.19
Summary: Low-level, data-driven core of boto 3.
Home-page: https://github.com/boto/botocore
Author: Amazon Web Services
Author-email: UNKNOWN
License: Apache License 2.0
Location: /usr/lib/python3/dist-packages
Requires:
Required-by:

Configuration

(Versions retrieved from inventory host, since boto is needed there. AWS CLI was installed after the run, before the version check (to get the expected results)

$ ansible-config dump --only-changed
DEFAULT_STDOUT_CALLBACK(/etc/ansible/ansible.cfg) = debug
DEFAULT_VAULT_IDENTITY_LIST(/etc/ansible/ansible.cfg) = [removed]
HOST_KEY_CHECKING(/etc/ansible/ansible.cfg) = False
$ pip show boto boto3 botocore
Name: boto
Version: 2.49.0
Summary: Amazon Web Services Library
Home-page: https://github.com/boto/boto/
Author: Mitch Garnaat
Author-email: mitch@garnaat.com
License: MIT
Location: /usr/lib/python3/dist-packages
Requires:
Required-by:
---
Name: boto3
Version: 1.34.99
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
Author-email: None
License: Apache License 2.0
Location: /home/gertvdb/.local/lib/python3.8/site-packages
Requires: botocore, s3transfer, jmespath
Required-by:
---
Name: botocore
Version: 1.34.99
Summary: Low-level, data-driven core of boto 3.
Home-page: https://github.com/boto/botocore
Author: Amazon Web Services
Author-email: None
License: Apache License 2.0
Location: /home/gertvdb/.local/lib/python3.8/site-packages
Requires: python-dateutil, jmespath, urllib3
Required-by: s3transfer, boto3

OS / Environment

Ubuntu 20.04 on WSL1

Steps to Reproduce

- name: If we have a bucket and no endpoint / region, fetch the endpoint from AWS
  amazon.aws.s3_bucket_info:
    name: "{{ backup_s3_bucket }}"
    transform_location: true
    access_key: "{{ backup_s3_key | default(omit) }}"
    secret_key: "{{ backup_s3_key_secret | default(omit) }}"
    bucket_facts:
      bucket_location: true
  register: s3_bucket_info
  when:
    - backup_s3_endpoint is not defined
    - backup_s3_region is not defined
    - backup_s3_bucket is defined
    - backup_s3_bucket != ''

Bucket policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "denyInsecureTransport",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::removed-backup-us-east-1-59c5daac/*",
                "arn:aws:s3:::removed-backup-us-east-1-59c5daac"
            ],
            "Condition": {
                "Bool": {
                    "aws:SecureTransport": "false"
                }
            }
        },
        {
            "Sid": "BucketAccess",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::account-num:role/NodeRole"
                ]
            },
            "Action": [
                "s3:Put*",
                "s3:List*",
                "s3:Get*",
                "s3:Delete*"
            ],
            "Resource": [
                "arn:aws:s3:::removed-backup-us-east-1-59c5daac/*",
                "arn:aws:s3:::removed-backup-us-east-1-59c5daac"
            ]
        }
    ]
}

Expected Results

s3_bucket_info:
  bucket_list:
    removed-backup-us-east-1-59c5daac:
      bucket_location:
        LocationConstraint: us-east-1

AWS CLI running with the same credentails (the instance's role) (the region in the name is not always accurate)

aws s3api get-bucket-location --bucket removed-backup-us-east-1-59c5daac
{
    "LocationConstraint": null
}

The role does not have permission to use ListBuckets:

# aws s3api list-buckets

An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

Actual Results

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
[WARNING]: packaging.version Python module not installed, unable to check AWS SDK versions
fatal: [mongo.qa1.iotnxt.io]: FAILED! => {
    "boto3_version": "1.9.253",
    "botocore_version": "1.16.19",
    "changed": false,
    "error": {
        "code": "AccessDenied",
        "message": "Access Denied"
    },
    "response_metadata": {
        "host_id": "2ULk7xa7GY0a/EwYUyXM1y4zpqbilq9BKJfvxpiYhBgt6x/VZ4Ij5oHIIf21tLsvJ8QaU9eKirk=",
        "http_headers": {
            "content-type": "application/xml",
            "date": "Fri, 05 Jul 2024 11:03:44 GMT",
            "server": "AmazonS3",
            "transfer-encoding": "chunked",
            "x-amz-id-2": "2ULk7xa7GY0a/EwYUyXM1y4zpqbilq9BKJfvxpiYhBgt6x/VZ4Ij5oHIIf21tLsvJ8QaU9eKirk=",
            "x-amz-request-id": "NK4NHDY8379WRAMB"
        },
        "http_status_code": 403,
        "request_id": "NK4NHDY8379WRAMB",
        "retry_attempts": 0
    }
}

MSG:

Failed to list buckets: An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
fatal: [removed]: FAILED! => {
    "boto3_version": "1.9.253",
    "botocore_version": "1.16.19",
    "changed": false,
    "error": {
        "code": "AccessDenied",
        "message": "Access Denied"
    },
    "response_metadata": {
        "host_id": "removed=",
        "http_headers": {
            "content-type": "application/xml",
            "date": "Fri, 05 Jul 2024 11:03:44 GMT",
            "server": "AmazonS3",
            "transfer-encoding": "chunked",
            "x-amz-id-2": removed=",
            "x-amz-request-id": "NK4K8S42FKNW1S00"
        },
        "http_status_code": 403,
        "request_id": "NK4K8S42FKNW1S00",
        "retry_attempts": 0
    }
}

MSG:

Failed to list buckets: An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

Code of Conduct

abikouo commented 3 months ago

@mohag it looks like the call to ListBuckets is superfluous when name is provided. I tried to fix it with #2184, could you please give it a try ?

mohag commented 2 months ago

That does work yes, thanks!

TASK [mongodb : If we have a bucket and no endpoint / region, fetch the endpoint from AWS] ******************************************************************************************************************************************
task path: /home/gertvdb/src/<removed>/ansible/roles/mongodb/tasks/pbm.yml:290
ok: [mongo.qa1.<removed>] => {
    "bucket_name": "<removed>-backup-us-east-1-59c5daac",
    "buckets": [
        {
            "bucket_location": {
                "LocationConstraint": "us-east-1"
            },
            "name": "<removed>-backup-us-east-1-59c5daac"
        }
    ],
    "changed": false
}

MSG:

Retrieved s3 info.

I installed the collection from git+https://github.com/abikouo/amazon.aws.git,s3_buckect_issue_2183

mohag commented 2 months ago

I also have a workaround that requires no bucket permissions / boto3 installation... (S3 redirects to the correct region for a bucket even without permissions, It also returns the correct region in a header, we use that here)

- name: Check the correct region from the "wrong endpoint" error message
  ansible.builtin.uri:
    url: "https://s3.dualstack.us-east-1.amazonaws.com/{{ backup_s3_bucket }}"
    follow_redirects: none
    status_code:
      - 301 # Wrong region
      - 403 # Correct region
  register: region_check_request
  when:
    - backup_s3_endpoint is not defined
    - backup_s3_region is not defined
    - backup_s3_bucket is defined
    - backup_s3_bucket != ''

- name: Get correct region from header on response
  ansible.builtin.set_fact:
    backup_s3_region: "{{ region_check_request.x_amz_bucket_region }}"
  when:
    - backup_s3_endpoint is not defined
    - backup_s3_region is not defined
    - backup_s3_bucket is defined
    - backup_s3_bucket != ''
tremble commented 2 months ago

I also have a workaround that requires no bucket permissions / boto3 installation... (S3 redirects to the correct region for a bucket even without permissions, this uses that)

In theory it would be possible to implement all of our modules without technically requiring boto3 (they're just calling an HTTP based API after all...). However, doing so would result in a massive amount of duplicate code. While I can see your use case, and I understand where you're coming from, there comes a point of diminishing returns.

Similarly, when it comes to working around missing permissions on a bucket, I can see the value in making the list_buckets call optional (technically it's not superfluous, it's returning creation_date too), but hacking around missing GetBucketLocation is something to poke Amazon about, not something we should be adding here. There's also no guarantee that at some point Amazon won't close that loophole.

mohag commented 2 months ago

I also have a workaround that requires no bucket permissions / boto3 installation... (S3 redirects to the correct region for a bucket even without permissions, this uses that)

In theory it would be possible to implement all of our modules without technically requiring boto3 (they're just calling an HTTP based API after all...). However, doing so would result in a massive amount of duplicate code. While I can see your use case, and I understand where you're coming from, there comes a point of diminishing returns.

Similarly, when it comes to working around missing permissions on a bucket, I can see the value in making the list_buckets call optional (technically it's not superfluous, it's returning creation_date too), but hacking around missing GetBucketLocation is something to poke Amazon about, not something we should be adding here. There's also no guarantee that at some point Amazon won't close that loophole.

I agree that that workaround does not make sense in the module, but it might be useful for someone else (and I have somewhere that I can find it was well in the future) (It was mainly my solution before the change to get rid of ListBuckets) (I currently install boto3 / botocore on the managed nodes just for this one step, so I'm thinking of keeping it)

Edit: The updated version no longer need xmltodict (It uses a header instead of parsing the response)