Repository: ansible/awx

Issue:
Conjur credential retrieval differs between Conjur OSS and Conjur Enterprise
### Please confirm the following

- [X] I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
- [X] I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.
- [X] I understand that AWX is open source software provided for free and that I might not receive a timely response.
- [X] I am **NOT** reporting a (potential) security vulnerability. (These should be emailed to `security@ansible.com` instead.)

### Bug Summary

When attempting to retrieve credentials using Ansible AWX 22.7.x from Conjur Enterprise 13, I consistently encounter a 401 error. After thorough investigation, I've identified that the root of the issue lies within the `conjur_backend` function, which executes two requests to Conjur in order to fetch the necessary credential information:

1. Initial authentication is performed through the endpoint `api/authn/<account>/<username>/authenticate/`. The response from this request includes a signature.
2. The actual credential retrieval takes place using the endpoint `api/secrets/<account>/variable/<path_to_secret>/`, where users provide the relevant variable or secret.

Upon closer inspection, it becomes clear that the authentication response (as described in point 1) differs between Conjur OSS and Conjur Enterprise. To illustrate these differences, I wrote a test script, and the subsequent output from the console is documented in the provided images (Image 1: Test Code, Image 2: Console Output).

Image 1:
<img width="848" alt="Screenshot 2023-08-24 at 10 48 32" src="https://github.com/ansible/awx/assets/10104752/57a82fb0-c2fa-4a69-96b0-3fe755e8dc59">

Image 2:
<img width="784" alt="Screenshot 2023-08-24 at 10 46 50" src="https://github.com/ansible/awx/assets/10104752/d0eeea49-d21a-4bd5-bd44-4df9fef7ab1b">


The Conjur Enterprise API returns a response containing the keys `signature`, `payload`, and `protected`. In contrast, the Conjur OSS response presents the same keys, encoded in base64, though. The result is that the plain message from Conjur Enterprise should be encoded to base64 first, before added as a header to the second request (point 2).

~~An alternative approach involves including the header `{"Accept-Encoding": "base64"}` within the headers for the second request. This adjustment is performed within the `awx/main/credential_plugins/conjur.py` file, wherein the modification is as follows:~~

Original Code:
```python
    lookup_kwargs = {
        'headers': {'Authorization': 'Token token="{}"'.format(token)},
        'allow_redirects': False,
    }
```

Modified Code:
```python
    lookup_kwargs = {
        'headers': {'Authorization': 'Token token="{}"'.format(token), 'Accept-Encoding': 'base64'},
        'allow_redirects': False,
    }
```

~~By incorporating this change, we ensure compatibility with both Conjur OSS and Conjur Enterprise instances, enhancing the credential retrieval process from AWX.~~

---

**EDIT:** Following further investigation into this issue, it appears that my initial solution of adding headers may not be effective. I attempted to rectify this within my local AWX environment, but unfortunately, the issue persisted.

A more viable approach could involve checking whether the authentication response is already in base64 format. This can be accomplished using the following steps:

1. Begin by importing the necessary modules:
```python
import base64
import binascii
```

2. Integrate the following function into your codebase:
```python
def _is_base64(s: str) -> bool:
    try:
        return base64.b64encode(base64.b64decode(s.encode("utf-8"))) == s.encode("utf-8")
    except binascii.Error:
        return False
```

3. Adjust the declaration of the `lookup_kwargs` as shown below. This modification ensures that the authentication token is properly encoded if it's not already in base64 format:
```python
lookup_kwargs = {
    'headers': {'Authorization': 'Token token="{}"'.format(
        token if _is_base64(token) else base64.b64encode(token.encode('utf-8')).decode('utf-8')
    )},
    'allow_redirects': False,
}
```

@infamousjoeg can you help with this? And do you agree with this change?

### AWX version

22.7.1

### Select the relevant components

- [ ] UI
- [ ] UI (tech preview)
- [ ] API
- [ ] Docs
- [ ] Collection
- [ ] CLI
- [X] Other

### Installation method

docker development environment

### Modifications

yes

### Ansible version

_No response_

### Operating system

MacOS

### Web browser

Chrome

### Steps to reproduce

- Setup a Conjur Enterprise and a Conjur OSS environment
- Create 2 credentials (both for Conjur Enterprice and OSS)
- Create identical users and variables in both environments
- Test the credential retrieval in both environments

### Expected results

The retrieval of a secret from OSS will work, but the retrieval of a secret from Enterprise will result in a 401 error.

### Actual results

The retrieval of a secret from OSS works, and the retrieval of a secret from Enterprise results in a 401 error.

### Additional information

I tested the suggested code change. That was the only change I did in AWX.


Discussion:
I encounter the same issue with Conjur EE 12.6 / AWX 22.1.0. Conjur OSS seems to work fine.
@zEktONO I'm looking into this - thanks for submitting!

Resolving patch:
diff --git a/awx/main/credential_plugins/conjur.py b/awx/main/credential_plugins/conjur.py
--- a/awx/main/credential_plugins/conjur.py
+++ b/awx/main/credential_plugins/conjur.py
@@ -4,6 +4,8 @@
 
 from django.utils.translation import gettext_lazy as _
 import requests
+import base64
+import binascii
 
 
 conjur_inputs = {
@@ -50,6 +52,13 @@
 }
 
 
+def _is_base64(s: str) -> bool:
+    try:
+        return base64.b64encode(base64.b64decode(s.encode("utf-8"))) == s.encode("utf-8")
+    except binascii.Error:
+        return False
+
+
 def conjur_backend(**kwargs):
     url = kwargs['url']
     api_key = kwargs['api_key']
@@ -77,7 +86,7 @@ def conjur_backend(**kwargs):
     token = resp.content.decode('utf-8')
 
     lookup_kwargs = {
-        'headers': {'Authorization': 'Token token="{}"'.format(token)},
+        'headers': {'Authorization': 'Token token="{}"'.format(token if _is_base64(token) else base64.b64encode(token.encode('utf-8')).decode('utf-8'))},
         'allow_redirects': False,
     }
 


Test patch: