Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OAuth2 ClientCredentials flow support #1013

Merged
merged 1 commit into from
Aug 22, 2024
Merged

Conversation

decko
Copy link
Member

@decko decko commented Jul 11, 2024

This work adds OAuth2 ClientCredentials grant flow support to the Pulp-CLI.
Closes #926


We're going with the short-term pragmatic approach, and reserving the right to completely refactor this whole area soon


As test strategy, we have two options here:

1- Use the responses lib (link here), a library that mocks the requests lib. With that we can intercept calls to a IdP mock to obtain a token, and then intercept the call to a Pulp instance and assert that there's the access_token being used with the Authorization header.

2- Mock an IdP using nginx rewrite module to return a 200 Ok message with an appropriate response to the token request. Together we need to configure the pulp server to provide the proper URL to allow pulp-cli to request a token and test an authorized operation on it.

decko added a commit to decko/pulp-cli that referenced this pull request Jul 22, 2024
@decko decko changed the title Add OAuth2 support Add OAuth2 ClientCredentials flow support Aug 14, 2024
@decko decko force-pushed the oauth2_support branch 2 times, most recently from 108a493 to 4462ff8 Compare August 14, 2024 13:03
@dkliban
Copy link
Member

dkliban commented Aug 14, 2024

@decko @mdellweg I used this PR to interact with our deployment that uses oauth2 for authentication. I set the username to client_id and the password to client_secret. I was able to create a domain, a repository, a remote, and sync the repository. Thanks for this awesome feature!

pulp-glue/pulp_glue/common/authentication.py Outdated Show resolved Hide resolved
pulp-glue/pulp_glue/common/authentication.py Outdated Show resolved Hide resolved
pulp-glue/pulp_glue/common/authentication.py Outdated Show resolved Hide resolved
pulp-glue/pulp_glue/common/authentication.py Show resolved Hide resolved
pulp-glue/pulp_glue/common/authentication.py Outdated Show resolved Hide resolved
pulp-glue/pulp_glue/common/authentication.py Outdated Show resolved Hide resolved
pulp-glue/pulp_glue/common/openapi.py Outdated Show resolved Hide resolved
pulp-glue/pulp_glue/common/openapi.py Outdated Show resolved Hide resolved
pulp-glue/pulp_glue/common/openapi.py Outdated Show resolved Hide resolved
pulpcore/cli/common/generic.py Outdated Show resolved Hide resolved
Comment on lines 52 to 69
class OpenAPISecurityScheme:
def __init__(self, security_scheme: SecurityScheme):
self.security_scheme = security_scheme

self.parse()

def parse(self) -> None:
self.type = self.security_scheme["type"]
self.description = self.security_scheme.get("description", "")

if self.type == "oauth2":
self.flows: OAuth2Flows = self.security_scheme.get("flows")
client_credentials: ClientCredentials = self.flows["clientCredentials"]
if client_credentials:
self.flow_type: t.Optional[str] = "clientCredentials"
self.token_url: str = client_credentials["tokenUrl"]
self.scopes: OAuth2FlowsScopes = list(client_credentials.get("scopes").keys())

if self.type == "http":
self.scheme = self.security_scheme["scheme"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all kind of hazy.
Can you explain why the logic didn't fit in the authproviderbase call method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the AuthProviderBase need to understand how to properly parse an OpenAPI SecurityScheme for each of those authentication methods? I was thinking on abstracting this to AuthProviderBase and exposes the minimum needed to choose which method to call.

@dkliban
Copy link
Member

dkliban commented Aug 15, 2024

I accidentally used wrong credentials and got the following traceback:

Traceback (most recent call last):
  File "/home/dkliban/insights-venv/bin/pulp", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulpcore/cli/common/generic.py", line 326, in invoke
    return super().invoke(ctx)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulpcore/cli/common/generic.py", line 326, in invoke
    return super().invoke(ctx)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulpcore/cli/common/generic.py", line 326, in invoke
    return super().invoke(ctx)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulpcore/cli/common/generic.py", line 326, in invoke
    return super().invoke(ctx)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)   
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/decorators.py", line 92, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/decorators.py", line 92, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulpcore/cli/common/generic.py", line 1289, in callback
    result = entity_ctx.list(limit=limit, offset=offset, parameters=kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulp-glue/pulp_glue/common/context.py", line 807, in list
    result: t.Mapping[str, t.Any] = self.call("list", parameters=payload)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulp-glue/pulp_glue/common/context.py", line 609, in call
    return self.pulp_ctx.call(
           ^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulp-glue/pulp_glue/common/context.py", line 375, in call
    result = self.api.call(
             ^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulp-glue/pulp_glue/common/openapi.py", line 778, in call
    response: requests.Response = self._session.send(request)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/requests/sessions.py", line 710, in send
    r = dispatch_hook("response", hooks, r, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/insights-venv/lib64/python3.12/site-packages/requests/hooks.py", line 30, in dispatch_hook
    _hook_data = hook(hook_data, **kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dkliban/devel/pulp-cli/pulp-glue/pulp_glue/common/authentication.py", line 44, in handle401
    self.retrieve_token()
  File "/home/dkliban/devel/pulp-cli/pulp-glue/pulp_glue/common/authentication.py", line 93, in retrieve_token
    self.token["issued_at"] = datetime.now().isoformat()
    ~~~~~~~~~~^^^^^^^^^^^^^
TypeError: 'NoneType' object does not support item assignment

@github-actions github-actions bot added the wip label Aug 19, 2024
@decko decko marked this pull request as ready for review August 20, 2024 21:48
@decko
Copy link
Member Author

decko commented Aug 20, 2024

Thanks @dkliban. We worked on that case and CLI is handling it fine now. Thanks.

@decko decko requested a review from mdellweg August 22, 2024 14:44
@ggainey ggainey dismissed mdellweg’s stale review August 22, 2024 17:32

Many of the changes made, and we're going to accept the result and refactor "soon"

Copy link
Contributor

@ggainey ggainey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review suggests we really want to refactor how AuthBase deals with handling/prioritizing multiple-arbitrary-schemes. However, that will have to wait. The PR in its current state answers a pressing existing need, and is "correct enough" to be useful. Approving, while recognizing this is truly "tech preview" and we are likely going to make significant changes in this code flow.

@ggainey ggainey merged commit 9c516bd into pulp:main Aug 22, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for issue a token from an IdentityProvider using OAuth2 or OpenIDConnect client_credential grant.
4 participants