diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 3df25170ac..5ea55da1b5 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -976,7 +976,10 @@ class InventorySourceOptions(BaseModel): 'Cloud-based inventory sources (such as %s) require ' 'credentials for the matching cloud service.' % self.source ) - elif self.source in CLOUD_PROVIDERS: + # Allow an EC2 source to omit the credential. If Tower is running on + # an EC2 instance with an IAM Role assigned, boto will use credentials + # from the instance metadata instead of those explicitly provided. + elif self.source in CLOUD_PROVIDERS and self.source != 'ec2': raise ValidationError('Credential is required for a cloud source') return cred diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 33df9992fa..572237c164 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -981,8 +981,9 @@ class RunInventoryUpdate(BaseTask): # sync with those in Ansible core at all times. passwords = kwargs.get('passwords', {}) if inventory_update.source == 'ec2': - env['AWS_ACCESS_KEY_ID'] = passwords.get('source_username', '') - env['AWS_SECRET_ACCESS_KEY'] = passwords.get('source_password', '') + if passwords.get('source_username', '') and passwords.get('source_password', ''): + env['AWS_ACCESS_KEY_ID'] = passwords['source_username'] + env['AWS_SECRET_ACCESS_KEY'] = passwords['source_password'] env['EC2_INI_PATH'] = kwargs.get('private_data_file', '') elif inventory_update.source == 'rax': env['RAX_CREDS_FILE'] = kwargs.get('private_data_file', '') diff --git a/awx/main/tests/inventory.py b/awx/main/tests/inventory.py index af188167d5..db6b7714ee 100644 --- a/awx/main/tests/inventory.py +++ b/awx/main/tests/inventory.py @@ -1286,6 +1286,12 @@ class InventoryUpdatesTest(BaseTransactionTest): with self.current_user(self.super_django_user): response = self.put(inv_src_url1, inv_src_data, expect=200) self.assertEqual(response['source_regions'], '') + # EC2 sources should allow an empty credential (to support IAM roles). + inv_src_data['credential'] = None + with self.current_user(self.super_django_user): + response = self.put(inv_src_url1, inv_src_data, expect=200) + self.assertEqual(response['credential'], None) + inv_src_data['credential'] = aws_cred_id # Null for instance filters and group_by should be converted to empty # string. inv_src_data['instance_filters'] = None