Skip to content

Conversation

rautenrieth-da
Copy link
Contributor

@rautenrieth-da rautenrieth-da commented Oct 2, 2025

Fixes https://github.com/DACH-NY/canton-network-internal/issues/1639

Summary of changes:

  • UserRightsProvider is a new trait for fetching the rights for a user
  • AuthExtractor has been refactored, now has several subclasses, each implementing on auth requirement:
    • AuthenticationOnlyExtractor: requires authenticated user, but no other special authorization. Useful for endpoints returning "public" data where we don't want to allow anonymous access for audit or rate limiting purposes.
    • UserWalletAuthExtractor: requires authenticated user, authorized to actAs the party associated with the user through a WalletAppInstall contract. Useful for endpoints that interact with some wallet.
    • AdminAuthExtractor: requires authenticated user, authorized to actAs the app operator party AND have participant admin rights
    • ActAsKnownPartyAuthExtractor: requires authenticated user, authorized to actAs a given party (usually the app service party)
  • Http*Handler: all endpoints in the same handler require the same auth. Auth is enforced by using one of the above auth extractors when constructing the routes, and the auth extractors returning unrelated types. As a result, some endpoints have moved:
    • Two endpoints from HttpWalletHandler have moved to HttpStatusWalletHandler because they don't require the user to have a wallet installed.
    • Sv app now has 3 handlers: HttpSvPublicHandler (no auth), HttpSvOperatorHandler (actAs operator party), HttpSvAdminHandler (actAs operator party AND participant admin)

@rautenrieth-da rautenrieth-da force-pushed the rautenrieth-da/api-auth branch 6 times, most recently from f188239 to 9623c67 Compare October 9, 2025 12:50
Signed-off-by: Robert Autenrieth <[email protected]>
Signed-off-by: Robert Autenrieth <[email protected]>
[ci]

Signed-off-by: Robert Autenrieth <[email protected]>
[ci]

Signed-off-by: Robert Autenrieth <[email protected]>
[ci]

Signed-off-by: Robert Autenrieth <[email protected]>
[ci]

Signed-off-by: Robert Autenrieth <[email protected]>
[ci]

Signed-off-by: Robert Autenrieth <[email protected]>
[ci]

Signed-off-by: Robert Autenrieth <[email protected]>
[ci]

Signed-off-by: Robert Autenrieth <[email protected]>
@rautenrieth-da rautenrieth-da force-pushed the rautenrieth-da/api-auth branch from 9623c67 to 47a27ad Compare October 9, 2025 15:50
@rautenrieth-da rautenrieth-da changed the title Rautenrieth da/api auth Improve authorization checks for CN app APIs Oct 9, 2025
@rautenrieth-da
Copy link
Contributor Author

/cluster_test

Copy link

github-actions bot commented Oct 9, 2025

Deploy cluster test triggered for Commit 47a27adcdec237f249b70c1a440b95e56186ccb1 in , please contact a Contributor to approve it in CircleCI: https://app.circleci.com/pipelines/github/DACH-NY/canton-network-internal/35811

@rautenrieth-da
Copy link
Contributor Author

/hdm_test

Copy link

github-actions bot commented Oct 9, 2025

Deploy HDM pipeline triggered for Commit 47a27adcdec237f249b70c1a440b95e56186ccb1 in , please contact a Contributor to approve it in CircleCI: https://app.circleci.com/pipelines/github/DACH-NY/canton-network-internal/35835

@rautenrieth-da rautenrieth-da marked this pull request as ready for review October 9, 2025 21:04
export VALIDATOR_AUTH_AUDIENCE

token=$("${VALIDATOR_DIR}/get-token.py" administrator)
token=$("${VALIDATOR_DIR}/get-token.py" ledger-api-user)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

administrator is the wallet user, ledger-api-user is the app operator user.

get:
tags: [wallet]
x-jvm-package: wallet
x-jvm-package: status.wallet
Copy link
Contributor Author

Choose a reason for hiding this comment

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

package wallet: requires actAs for the wallet party
package status.wallet: requires just an authenticated user, no rights checked

get:
tags: [sv]
x-jvm-package: sv_admin
x-jvm-package: sv_operator
Copy link
Contributor Author

Choose a reason for hiding this comment

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

package sv_public: au auth whatsoever
package sv_operator: requires actAs on the sv operator party
package sv_admin: requires actAs on the sv operator party and participant admin

consoleEnvironment.run {
httpCommand(
HttpSvAppClient.OnboardValidator(validator, secret, BuildInfo.compiledVersion, contactPoint)
HttpSvPublicAppClient.OnboardValidator(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe even call it HttpSvNoAuthAppClient (don't know which one is scarier)

def cometBftNodeStatus(): definitions.CometBftNodeStatusResponse =
consoleEnvironment.run {
httpCommand(HttpSvAppClient.GetCometBftNodeStatus())
httpCommand(HttpSvPublicAppClient.GetCometBftNodeStatus())
Copy link
Contributor

@ray-roestenburg-da ray-roestenburg-da Oct 10, 2025

Choose a reason for hiding this comment

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

why is the node status public? (just curious)

): definitions.CometBftJsonRpcResponse =
consoleEnvironment.run {
httpCommand(HttpSvAppClient.CometBftJsonRpcRequest(id, method, params))
httpCommand(HttpSvPublicAppClient.CometBftJsonRpcRequest(id, method, params))
Copy link
Contributor

Choose a reason for hiding this comment

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

This also seems strange to be public?

)
}

@Help.Summary("Prepare a validator onboarding and return an onboarding secret (via admin API)")
Copy link
Contributor

Choose a reason for hiding this comment

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

is the comment still correct? (via admin API)

isDeactivated = false,
)
.id
assertRejectedAsUnauthorized(userId)
Copy link
Contributor

Choose a reason for hiding this comment

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

nice cleanup thanks

* ==Authorization==
*
* - user must exist on the participant and be active
* - primary party must be set for the user and equal to the app operator party
Copy link
Contributor

Choose a reason for hiding this comment

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

you say app operator party, but it is actually the party parameter on this class right? slightly confusing cause you mix where this is used and what is defined here I think, rather refer to party with backticks

Copy link
Contributor

@ray-roestenburg-da ray-roestenburg-da Oct 10, 2025

Choose a reason for hiding this comment

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

(and use @param in the doc if needed)

* ==Authorization==
*
* - user must exist on the participant and be active
* - primary party must be set for the user and equal to the app operator party
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* - primary party must be set for the user and equal to the app operator party
* - primary party must be set for the user and equal to the `adminParty`

* - user must exist on the participant and be active
* - primary party must be set for the user and equal to the app operator party
* - user must have actAs rights for the app operator party
* - user must have ParticipantAdmin rights
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* - user must have ParticipantAdmin rights
* - user must have ParticipantAdmin rights
@param adminParty the app operator party

authenticateLedgerApiUser(operationId)
.flatMap { authenticatedUser =>
onComplete(
rightsProvider.getUser(authenticatedUser) zip rightsProvider.listUserRights(
Copy link
Contributor

Choose a reason for hiding this comment

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

if you always do this zip, you might as well change the rightsProvider and have a getUserAndRights or something like that

import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}

class UncachedUserRightsProvider(
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick but I would just call this something like LedgerUserRightsProvider

get:
tags: [sv]
x-jvm-package: sv
x-jvm-package: sv_public
Copy link
Contributor

Choose a reason for hiding this comment

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

So not sure about this one, why that's needed to be fully public

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This endpoint didn't have any auth before, here we're just renaming things to make it more clear.

Ideally this would have some kind of auth, but CometBFT will be replaced by a different consensus layer in the near future and we wanted to limit the number of breaking changes, so I left it as is.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok

post:
tags: [sv]
x-jvm-package: sv
x-jvm-package: sv_public
Copy link
Contributor

Choose a reason for hiding this comment

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

So not sure about this one, why that's needed to be fully public

"$ref": "#/components/schemas/OnboardSvSequencerResponse"
"400":
$ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/400"
/v0/devnet/onboard/validator/prepare:
Copy link
Contributor

@ray-roestenburg-da ray-roestenburg-da Oct 10, 2025

Choose a reason for hiding this comment

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

Can we prevent this API and other devnet-only ones to exist on non-devnet? or would we need to do many openapi spec files or something like that if we really wanted to? (or do we do, or can we do, a proxy thing that 'removes this' and returns notfound or something on non-devnet)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The implementation returns a 501 Not Implemented on any non-devnet network where the isDevNet flag is false. Do you want to remove the endpoint because we don't trust this check, or because we want to avoid parsing the request in the first place?

Copy link
Contributor

Choose a reason for hiding this comment

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

avoid handling it, but I guess it's not really important, I just thought it would be nicer if it responded like any other API that is not there

* passing their onboarding secret in the request payload.
*
* Protection: The onboarding secret is unguessable and single-use.
* Note that we are still spending some resources on verifying the secret.
Copy link
Contributor

Choose a reason for hiding this comment

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

So this is an easy target for an attack I guess.. but can prevent issues with rate limit etc.

respond: v0.SvResource.GetSvOnboardingStatusResponse.type
/** Intended use: Used by other SV operators
*
* Protection: Endpoint is protected by IP allowlisting
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment will get out of date..

Copy link
Contributor

Choose a reason for hiding this comment

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

why is this not sv operator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Intended use: Used by other SV operators

This is called by other SVs to onboard their node. Other SVs do not have any token for your sv app, so this can't have auth. We have to rely on IP whitelisting instead (like we do for many other requests between SV nodes).

implicit val tc = extracted
/** Intended use: The SV app UI.
*
* Protection: None for backwards compatibility reasons
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe create an issue to deprecate and move

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's one on the line below 🙂

respond: v0.SvResource.GetCometBftNodeStatusResponse.type
/** Intended use: Interacting with CometBFT node monitoring/debugging/testing purposes
*
* Protection: Endpoint is protected by IP allowlisting
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't this move to sv_operator as well? (or plan to make a move)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above: cometbft will be replaced, changing auth here is not worth the effort.

respond: v0.SvResource.OnboardSvSequencerResponse.type
/** Intended use: Used by other SV operators
*
* Protection: Endpoint is protected by IP allowlisting
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I would remove these comments

Copy link
Contributor

Choose a reason for hiding this comment

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

(maybe change it to a TODO to move it out of public)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above, endpoints that are called by other svs can't have auth.

walletManager,
userRightsProvider,
loggerFactory,
"splice wallet realm",
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick extract to constant / val

mat: Materializer,
tracer: Tracer,
) extends v0.ScanproxyHandler[TracedUser]
) extends v0.ScanproxyHandler[AuthenticatedRequest]
Copy link
Contributor

Choose a reason for hiding this comment

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

nice

mat: Materializer,
tracer: Tracer,
) extends v0.ValidatorAdminHandler[TracedUser]
) extends v0.ValidatorAdminHandler[AdminUserRequest]
Copy link
Contributor

Choose a reason for hiding this comment

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

nice, like it.

.flatMap { authenticatedUser =>
onComplete(
// Double zip is a bit ugly...
rightsProvider.getUser(authenticatedUser) zip rightsProvider.listUserRights(
Copy link
Contributor

Choose a reason for hiding this comment

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

if you change rightsProvide to have one method for this there is one less zip :-)


/** @param user The authenticated user name.
* @param userWallet The wallet associated with the authenticated user.
* We need to look up the wallet in the extractor to get the end user party,
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: here you are describing what code somewhere else is doing, I would prefer to document that differently, this should just describe the field

Revoking user rights on the participant will revoke access to the corresponding API endpoints.

In general, endpoints that required authentication before will now check that the authenticated user
is not deactivated on the participant has has ``actAs`` rights for the relevant party
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
is not deactivated on the participant has has ``actAs`` rights for the relevant party
is not deactivated on the participant has ``actAs`` rights for the relevant party

rights may use the SV or wallet UIs, but may not perform administrative actions like
hard synchronizer migrations.

- ``/v0/admin/domain/pause``
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this list should follow directly after

The following SV app endpoints now require the user to have participant admin rights in
the participant user management service.

And after the list the next paragraph

- ``/v0/admin/domain/data-snapshot``

Note that only the service users of the SV and validator apps should automatically have participant admin rights.
If you are using other users to access the above endpoints, check their rights.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
If you are using other users to access the above endpoints, check their rights.
If you are using other users to access the above endpoints, please check their rights.


- SV app ``/v0/dso`` is currently public, but will require authorization as SV operator,
similar to most other SV app endpoints.
Use the corresponding public endpoint in the scan app if you need to fetch DSO info.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just state the actual endpoint in scan

Suggested change
Use the corresponding public endpoint in the scan app if you need to fetch DSO info.
Use the public ``/v0/dso`` endpoint in the scan app if you need to fetch DSO info.

Copy link
Contributor

@ray-roestenburg-da ray-roestenburg-da left a comment

Choose a reason for hiding this comment

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

Some minor comments, thanks, looks good!

@moritzkiefer-da
Copy link
Contributor

@rautenrieth-da this change seems somewhat risky in terms of breaking some of the workflows SVs may be used to and non-urgent. Any reason not to postpone until after the 0.5 migration?

@rautenrieth-da
Copy link
Contributor Author

this change seems somewhat risky in terms of breaking some of the workflows SVs may be used to and non-urgent. Any reason not to postpone until after the 0.5 migration?

The PR will probably quickly conflict with changes on main because it moved around many methods in the Http*Handler classes. Other than that, there's no reason.

I agree that it could break some workflows, hence the detailed changelog entry. I'll leave it open and try to keep it up to date with main until 0.5.0 then.

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