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

Authn-JWT Refactor #2734

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open

Authn-JWT Refactor #2734

wants to merge 25 commits into from

Conversation

jvanderhoof
Copy link
Contributor

@jvanderhoof jvanderhoof commented Mar 3, 2023

This work is an effort to port the Authn-JWT functionality to the new Authenticator architecture.

Desired Outcome

Refactor Authn-JWT to use the new authenticator architecture defined in the new OIDC authenticator.

Implemented Changes

The authenticator has been completely ported, with all previous integration tests passing. I've added a Readme which overviews the architecture and interfaces.

Connected Issue/Story

N/A

Definition of Done

Changelog

  • The CHANGELOG has been updated, or
  • This PR does not include user-facing changes and doesn't require a
    CHANGELOG update

Test coverage

  • This PR includes new unit and integration tests to go with the code
    changes, or
  • The changes in this PR do not require tests

Documentation

  • Docs (e.g. READMEs) were updated in this PR
  • A follow-up issue to update official docs has been filed here: [insert issue ID]
  • This PR does not require updating any documentation

Behavior

  • This PR changes product behavior and has been reviewed by a PO, or
  • These changes are part of a larger initiative that will be reviewed later, or
  • No behavior was changed with this PR

Security

  • Security architect has reviewed the changes in this PR,
  • These changes are part of a larger initiative with a separate security review, or
  • There are no security aspects to these changes

@jvanderhoof jvanderhoof force-pushed the authn-jwt-refactor branch 3 times, most recently from 69a6f88 to 34f63f7 Compare March 10, 2023 18:17
Copy link
Member

@rafis3 rafis3 left a comment

Choose a reason for hiding this comment

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

Thanks for this design @jvanderhoof.
It's really visible in your implementation, of how much the different steps of the authenticator logic is organized and easier to understand. I added a few comments and questions, would love for you to take a look.

end
```

which defines the required and optional data as well as the type. As Conjur Variables store values as strings, they type will always be `String`.
Copy link
Member

Choose a reason for hiding this comment

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

Question: while all variables are strings, some of them hold a JSON structure. For example, in the JWT authenticator, the public-keys variable expects a structure like this:

{
  "type": "jwks",
  "value": <JWKS VALUE AS DEFINED BY THE IDENTITY PROVIDER>
}

Do you think that the schema should somehow define that as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really like this idea because the public-keys implementation was one of the hardest bits to reverse engineer.

Dry Validations use Dry RB's Schema library, which supports custom types: https://dry-rb.org/gems/dry-schema/1.10/advanced/custom-types/. Schema also supports nested validation: https://dry-rb.org/gems/dry-schema/1.10/nested-data/. I'll need to dig in some more, but it looks likely that we could perform some validation on the provided JSON (if defined). This would give us the ability to guide the end user on the format of their public keys data.

#
# @param [Hash/String] identifier - the role identifier established by the Strategy
# @param [String] account - request account
# @param [Array] allowed_roles - an array of roles with permission to authenticate using this authenticator
Copy link
Member

Choose a reason for hiding this comment

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

I assume that the allowed_roles are given so that the Identity resolver will not need to access the DB directly?
I have a concern that if this authenticator has 30K roles that are allowed to it, such array will be quite a memory consumer and also the copying process of such amount of data will probably not be efficient for the Ruby runtime.

WDYT?

Copy link
Contributor Author

@jvanderhoof jvanderhoof Mar 20, 2023

Choose a reason for hiding this comment

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

I went back and forth a LOT on this during the design of the code redirect OIDC authentication flow. Ultimately I decided the simplicity of the approach (and critically, removing the DB awareness from the scope of the Strategy) was more important in the short term.

This question hints at a ResolveIdentity improvement I'm trying to figure out how best to implement (and it would solve the above concern). Ultimately, I'd like the ResolveIdentity.call method to be called for each role allowed to authenticate. The call method would return either True or False depending on if the match is successful or not.

This solves two problems. The first, as noted above, is that it allows us to stream a set of roles (resolving the memory "bloat" potential. The second, is it allows us to use annotations for re-mapping email address to users (and enable OIDC users to be kept outside the root policy. Andy's security concern is that multiple matches need to be identified and flagged (we can't exit on the first identified match). "Streaming" the role match allows us to guarantee only one match exists.

I need to find a good way to allow us to perform setup activity (example) before transforming call into a method which takes a block.

# @param [String] request_body - authentication request body
# @param [Hash] parameters - authentication request parameters
#
# @return something suitable for identifying a Conjur Role (usually a String or Hash)
Copy link
Member

Choose a reason for hiding this comment

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

If the Strategy is the code that has the authentication logic itself, maybe it needs to respond a decision? Like true if the authentication was successful, false if not. And additionally an optional string, with a reason to why an authentication failed?
I am trying to think if it makes sense for the strategy to be more strict in what it returns, so that the correct logic (authentication decision making) will be implemented into it. I see that in your implementation, it returns the JWT, right? Can you please explain why is that needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of the JWT Authenticator, we return the relevant part of the JWT token. Initially I was thinking is is because we perform some extraction in the Identity Resolution phase. In the OIDC Authenticator, we extract the desired lookup attribute from the claim (and return that).

Until you asked the question, I didn't think about the fact that the "pre-filter" logic probably should live in the Strategy (and Strategies need to return a single identity to be mapped to a Conjur Role). This allows us to keep the implementation details (JWT/OIDC/AWS IAM, etc.) in the Strategy.

This change also enables the above refactor (streaming role match) to be significantly simpler.

Copy link
Contributor Author

@jvanderhoof jvanderhoof Mar 20, 2023

Choose a reason for hiding this comment

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

Scratch part of this above statement. There is key information in the JWT that needs to be available in the Identity Resolution phase (ex annotation matching). Returning the relevant bits of the validated JWT ensures the secondary check that at least one mapping annotation exists on a host can occur.

@logger = logger
end

def call(identifier:, allowed_roles:, id: nil)
Copy link

Choose a reason for hiding this comment

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

Method call has 67 lines of code (exceeds 25 allowed). Consider refactoring.

end

# Verify that `ca_cert` has a secret value set if the variable is present
rule(:ca_cert, :account, :service_id) do
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 9 locations. Consider refactoring.

end

# Verify that `enforced_claims` has a secret value set if variable is present
rule(:enforced_claims, :service_id, :account) do
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 9 locations. Consider refactoring.

end

# Verify that `identity_path` has a secret value set if variable is present
rule(:identity_path, :service_id, :account) do
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 9 locations. Consider refactoring.

end

# Verify that `audience` has a secret value set if variable is present
rule(:audience, :service_id, :account) do
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 9 locations. Consider refactoring.

@jvanderhoof jvanderhoof force-pushed the authn-jwt-refactor branch 2 times, most recently from b8aa76a to 7652fef Compare March 31, 2023 02:31
Copy link

@codeclimate codeclimate bot left a comment

Choose a reason for hiding this comment

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

The PR diff size of 12507 lines exceeds the maximum allowed for the inline comments feature.

@jvanderhoof jvanderhoof marked this pull request as ready for review April 24, 2023 16:58
@jvanderhoof jvanderhoof requested a review from a team as a code owner April 24, 2023 16:58
Copy link

@codeclimate codeclimate bot left a comment

Choose a reason for hiding this comment

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

The PR diff size of 12513 lines exceeds the maximum allowed for the inline comments feature.

@codeclimate
Copy link

codeclimate bot commented Apr 24, 2023

Code Climate has analyzed commit 29dcd27 and detected 204 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 114
Duplication 17
Bug Risk 6
Style 66
Security 1

The test coverage on the diff in this pull request is 93.4% (50% is the threshold).

This pull request will bring the total coverage in the repository to 89.2% (-2.5% change).

View more on Code Climate.

This commit migrates the existing authn-jwt functionality to the new Strategy/ResolveIdentity architecture.
This commit updates the previously implemented authn-oidc workflow to adhere to the small changes in interface
defined in the authn-jwt refactor
This commit removes the previous authn-jwt implementation and unit tests.
@jvanderhoof jvanderhoof force-pushed the authn-jwt-refactor branch from 5c83479 to e2584f9 Compare May 24, 2023 12:50
jvanderhoof and others added 8 commits May 24, 2023 13:41
This commit:
  - Adds support for regional STS endpoints.
  - Improves log messaging for failed requests.
  - Adds unit tests for all authenticator functionality.
Conjur is currently the only project which uses the conjur-rack gem. This PR brings it into Conjur to simplify
the authorization process.
This commit:
- Replaces a call to `git` with the equivalent Ruby call. This prevents a Git permission error.
- Addresses all Rubocop warnings.
@szh
Copy link
Contributor

szh commented Feb 7, 2025

@jvanderhoof Can this PR be closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants