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

feat(prism-agent): add multi-tenant wallet self-service capability #779

Merged
merged 19 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions infrastructure/shared/keycloak/init-script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ create_realm $ADMIN_ACCESS_TOKEN
echo "Creating a new prism-agent client ..."
create_client $ADMIN_ACCESS_TOKEN "prism-agent" $PRISM_AGENT_CLIENT_SECRET

echo "Creating a new prism-manage client ..."
create_client $ADMIN_ACCESS_TOKEN "prism-manage" $PRISM_AGENT_CLIENT_SECRET

echo "Creating a new sample user ..."
create_user $ADMIN_ACCESS_TOKEN "alice" "1234"

echo "Creating a new sample user ..."
create_user $ADMIN_ACCESS_TOKEN "bob" "1234"
21 changes: 1 addition & 20 deletions prism-agent/service/server/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ agent {
# autoUpgradeToRPT is used to enable the auto RPT (requesting party token) logic.
# if enabled, normal accessToken can be used to perform permission checks by obtaining RPT from accessToken.
# if disabled, accessToken must be RPT which already include the permission claims.
autoUpgradeToRPT = false
autoUpgradeToRPT = true
autoUpgradeToRPT = ${?KEYCLOAK_UMA_AUTO_UPGRADE_RPT}
}
}
Expand Down Expand Up @@ -219,22 +219,3 @@ agent {
authApiKey = ${?DEFAULT_WALLET_AUTH_API_KEY}
}
}

keycloakAdmin {
serverUrl = "http://localhost:8080/auth",
serverUrl = ${?KEYCLOAK_SERVER_URL}
realm = "master",
realm = ${?KEYCLOAK_REALM}
username = "admin",
username = ${?KEYCLOAK_ADMIN_USERNAME}
password = "admin",
password = ${?KEYCLOAK_ADMIN_PASSWORD}
clientId = "admin-cli",
clientId = ${?KEYCLOAK_ADMIN_CLIENT_ID}
clientSecret = "",
clientSecret = ${?KEYCLOAK_ADMIN_CLIENT_SECRET}
authToken= "",
authToken = ${?KEYCLOAK_ADMIN_AUTH_TOKEN}
scope= ""
scope = ${?KEYCLOAK_ADMIN_SCOPE}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import io.iohk.atala.event.controller.EventControllerImpl
import io.iohk.atala.event.notification.EventNotificationServiceImpl
import io.iohk.atala.iam.authentication.DefaultAuthenticator
import io.iohk.atala.iam.authentication.apikey.JdbcAuthenticationRepository
import io.iohk.atala.iam.authorization.DefaultPermissionManagementService
import io.iohk.atala.iam.authorization.core.EntityPermissionManagementService
import io.iohk.atala.iam.entity.http.controller.{EntityController, EntityControllerImpl}
import io.iohk.atala.iam.wallet.http.controller.WalletManagementControllerImpl
import io.iohk.atala.issue.controller.IssueControllerImpl
Expand Down Expand Up @@ -155,7 +157,10 @@ object MainApp extends ZIOAppDefault {
// authentication
AppModule.builtInAuthenticatorLayer,
AppModule.keycloakAuthenticatorLayer,
AppModule.keycloakPermissionManagementLayer,
DefaultAuthenticator.layer,
DefaultPermissionManagementService.layer,
EntityPermissionManagementService.layer,
// grpc
GrpcModule.prismNodeStubLayer,
// storage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,21 @@ import io.iohk.atala.iam.authentication.apikey.ApiKeyAuthenticator
import io.iohk.atala.iam.authentication.apikey.ApiKeyAuthenticatorImpl
import io.iohk.atala.iam.authentication.apikey.ApiKeyConfig
import io.iohk.atala.iam.authentication.apikey.AuthenticationRepository
import io.iohk.atala.iam.authentication.oidc.KeycloakAuthenticator
import io.iohk.atala.iam.authentication.oidc.KeycloakAuthenticatorImpl
import io.iohk.atala.iam.authentication.oidc.KeycloakClientImpl
import io.iohk.atala.iam.authentication.oidc.KeycloakConfig
import io.iohk.atala.iam.authentication.oidc.KeycloakEntity
import io.iohk.atala.iam.authorization.core.PermissionManagement
import io.iohk.atala.iam.authorization.keycloak.admin.KeycloakPermissionManagementService
import io.iohk.atala.pollux.vc.jwt.{PrismDidResolver, DidResolver as JwtDidResolver}
import io.iohk.atala.prism.protos.node_api.NodeServiceGrpc
import io.iohk.atala.shared.db.{ContextAwareTask, DbConfig, TransactorLayer}
import org.keycloak.authorization.client.AuthzClient
import zio.*
import zio.config.typesafe.TypesafeConfigSource
import zio.config.{ReadError, read}
import zio.http.Client
import io.iohk.atala.iam.authentication.oidc.KeycloakAuthenticator

object SystemModule {
val configLayer: Layer[ReadError[String], AppConfig] = ZLayer.fromZIO {
Expand Down Expand Up @@ -74,20 +78,45 @@ object AppModule {
ApiKeyAuthenticatorImpl.layer,
)

val keycloakAuthenticatorLayer: RLayer[AppConfig & WalletManagementService & Client, KeycloakAuthenticator] =
val keycloakAuthenticatorLayer: RLayer[
AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
KeycloakAuthenticator
] =
ZLayer.fromZIO {
ZIO
.serviceWith[AppConfig](_.agent.authentication.keycloak.enabled)
.map { isEnabled =>
if (!isEnabled) KeycloakAuthenticatorImpl.disabled
else
ZLayer.makeSome[AppConfig & WalletManagementService & Client, KeycloakAuthenticator](
ZLayer.makeSome[
AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
KeycloakAuthenticator
](
KeycloakConfig.layer,
KeycloakAuthenticatorImpl.layer,
KeycloakClientImpl.authzClientLayer,
KeycloakClientImpl.layer
)
}
}.flatten

val keycloakPermissionManagementLayer
: RLayer[AppConfig & WalletManagementService & Client, PermissionManagement.Service[KeycloakEntity]] = {
ZLayer.fromZIO {
ZIO
.serviceWith[AppConfig](_.agent.authentication.keycloak.enabled)
.map { isEnabled =>
if (!isEnabled) KeycloakPermissionManagementService.disabled
else
ZLayer.makeSome[AppConfig & WalletManagementService & Client, PermissionManagement.Service[KeycloakEntity]](
KeycloakClientImpl.authzClientLayer,
KeycloakClientImpl.layer,
KeycloakConfig.layer,
KeycloakPermissionManagementService.layer
)
}
}.flatten
}
}

object GrpcModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.iohk.atala.pollux.credentialschema.{SchemaRegistryServerEndpoints, Ver
import io.iohk.atala.pollux.vc.jwt.DidResolver as JwtDidResolver
import io.iohk.atala.presentproof.controller.PresentProofServerEndpoints
import io.iohk.atala.resolvers.DIDResolver
import io.iohk.atala.shared.models.WalletAdministrationContext
import io.iohk.atala.shared.models.{HexString, WalletAccessContext, WalletId}
import io.iohk.atala.shared.utils.DurationOps.toMetricsSeconds
import io.iohk.atala.system.controller.SystemServerEndpoints
Expand Down Expand Up @@ -104,6 +105,7 @@ object PrismAgentApp {
.catchAll(e => ZIO.logError(s"error while syncing DID publication state: $e"))
.repeat(Schedule.spaced(10.seconds))
.unit
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.Admin()))

}

Expand Down Expand Up @@ -153,6 +155,7 @@ object AgentInitialization {
for {
_ <- validateAppConfig
_ <- initializeDefaultWallet
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.Admin()))
} yield ()

private val validateAppConfig =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.iohk.atala.agent.server.config

import io.iohk.atala.castor.core.model.did.VerificationRelationship
import io.iohk.atala.iam.authentication.AuthenticationConfig
import io.iohk.atala.iam.authorization.keycloak.admin.KeycloakAdminConfig
import io.iohk.atala.pollux.vc.jwt.*
import io.iohk.atala.shared.db.DbConfig
import zio.config.*
Expand All @@ -18,7 +17,6 @@ final case class AppConfig(
agent: AgentConfig,
connect: ConnectConfig,
prismNode: PrismNodeConfig,
keycloakAdmin: KeycloakAdminConfig
) {
def validate: Either[String, Unit] =
for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import zio.*

class DIDRegistrarServerEndpoints(
didRegistrarController: DIDRegistrarController,
authenticator: Authenticator[BaseEntity] & Authorizer[BaseEntity]
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {

private val listManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.listManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, paginationInput) =>
didRegistrarController
Expand All @@ -27,7 +28,7 @@ class DIDRegistrarServerEndpoints(

private val createManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.createManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, createManagedDidRequest) =>
didRegistrarController
Expand All @@ -38,7 +39,7 @@ class DIDRegistrarServerEndpoints(

private val getManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.getManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did) =>
didRegistrarController
Expand All @@ -49,7 +50,7 @@ class DIDRegistrarServerEndpoints(

private val publishManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.publishManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did) =>
didRegistrarController
Expand All @@ -60,7 +61,7 @@ class DIDRegistrarServerEndpoints(

private val updateManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.updateManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did, updateRequest) =>
didRegistrarController
Expand All @@ -71,7 +72,7 @@ class DIDRegistrarServerEndpoints(

private val deactivateManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.deactivateManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did) =>
didRegistrarController
Expand All @@ -96,7 +97,7 @@ object DIDRegistrarServerEndpoints {
for {
authenticator <- ZIO.service[DefaultAuthenticator]
didRegistrarController <- ZIO.service[DIDRegistrarController]
didRegistrarEndpoints = new DIDRegistrarServerEndpoints(didRegistrarController, authenticator)
didRegistrarEndpoints = new DIDRegistrarServerEndpoints(didRegistrarController, authenticator, authenticator)
} yield didRegistrarEndpoints.all
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import java.util.UUID

class ConnectionServerEndpoints(
connectionController: ConnectionController,
authenticator: Authenticator[BaseEntity] & Authorizer[BaseEntity]
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {

private val createConnectionServerEndpoint: ZServerEndpoint[Any, Any] =
createConnection
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, request: CreateConnectionRequest) =>
connectionController
Expand All @@ -32,7 +33,7 @@ class ConnectionServerEndpoints(

private val getConnectionServerEndpoint: ZServerEndpoint[Any, Any] =
getConnection
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, connectionId: UUID) =>
connectionController
Expand All @@ -43,7 +44,7 @@ class ConnectionServerEndpoints(

private val getConnectionsServerEndpoint: ZServerEndpoint[Any, Any] =
getConnections
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, paginationInput: PaginationInput, thid: Option[String]) =>
connectionController
Expand All @@ -54,7 +55,7 @@ class ConnectionServerEndpoints(

private val acceptConnectionInvitationServerEndpoint: ZServerEndpoint[Any, Any] =
acceptConnectionInvitation
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, request: AcceptConnectionInvitationRequest) =>
connectionController
Expand All @@ -76,7 +77,7 @@ object ConnectionServerEndpoints {
for {
authenticator <- ZIO.service[DefaultAuthenticator]
connectionController <- ZIO.service[ConnectionController]
connectionEndpoints = new ConnectionServerEndpoints(connectionController, authenticator)
connectionEndpoints = new ConnectionServerEndpoints(connectionController, authenticator, authenticator)
} yield connectionEndpoints.all
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import io.iohk.atala.iam.authentication.SecurityLogic

class EventServerEndpoints(
eventController: EventController,
authenticator: Authenticator[BaseEntity] & Authorizer[BaseEntity]
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {

val createWebhookNotificationServerEndpoint: ZServerEndpoint[Any, Any] =
EventEndpoints.createWebhookNotification
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, createWebhook) =>
eventController
Expand All @@ -27,7 +28,7 @@ class EventServerEndpoints(

val listWebhookNotificationServerEndpoint: ZServerEndpoint[Any, Any] =
EventEndpoints.listWebhookNotification
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac => rc =>
eventController
.listWebhookNotifications(rc)
Expand All @@ -36,7 +37,7 @@ class EventServerEndpoints(

val deleteWebhookNotificationServerEndpoint: ZServerEndpoint[Any, Any] =
EventEndpoints.deleteWebhookNotification
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, id) =>
eventController
Expand All @@ -58,7 +59,7 @@ object EventServerEndpoints {
for {
authenticator <- ZIO.service[DefaultAuthenticator]
eventController <- ZIO.service[EventController]
eventEndpoints = new EventServerEndpoints(eventController, authenticator)
eventEndpoints = new EventServerEndpoints(eventController, authenticator, authenticator)
} yield eventEndpoints.all
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package io.iohk.atala.iam.authentication
import io.iohk.atala.agent.walletapi.model.BaseEntity
import io.iohk.atala.agent.walletapi.model.Entity
import io.iohk.atala.api.http.ErrorResponse
import io.iohk.atala.shared.models.WalletAccessContext
import io.iohk.atala.shared.models.WalletAdministrationContext
import io.iohk.atala.shared.models.WalletId
import zio.{IO, ZIO, ZLayer}

Expand Down Expand Up @@ -43,14 +45,22 @@ trait Authenticator[E <: BaseEntity] {
}

trait Authorizer[E <: BaseEntity] {
def authorize(entity: E): IO[AuthenticationError, WalletId]
def authorize(entity: E): IO[AuthenticationError, WalletAccessContext]
def authorizeWalletAdmin(entity: E): IO[AuthenticationError, WalletAdministrationContext]
}

object EntityAuthorizer extends EntityAuthorizer

trait EntityAuthorizer extends Authorizer[Entity] {
override def authorize(entity: Entity): IO[AuthenticationError, WalletId] =
ZIO.succeed(entity.walletId).map(WalletId.fromUUID)
override def authorize(entity: Entity): IO[AuthenticationError, WalletAccessContext] =
ZIO.succeed(entity.walletId).map(WalletId.fromUUID).map(WalletAccessContext.apply)

override def authorizeWalletAdmin(entity: Entity): IO[AuthenticationError, WalletAdministrationContext] = {
val ctx =
if (entity == Entity.Admin) WalletAdministrationContext.Admin()
else WalletAdministrationContext.SelfService(Seq(WalletId.fromUUID(entity.walletId)))
ZIO.succeed(ctx)
}
}

trait AuthenticatorWithAuthZ[E <: BaseEntity] extends Authenticator[E], Authorizer[E]
Expand All @@ -59,8 +69,10 @@ object DefaultEntityAuthenticator extends AuthenticatorWithAuthZ[BaseEntity] {

override def isEnabled: Boolean = true
override def authenticate(credentials: Credentials): IO[AuthenticationError, BaseEntity] = ZIO.succeed(Entity.Default)
override def authorize(entity: BaseEntity): IO[AuthenticationError, WalletId] =
override def authorize(entity: BaseEntity): IO[AuthenticationError, WalletAccessContext] =
EntityAuthorizer.authorize(Entity.Default)
override def authorizeWalletAdmin(entity: BaseEntity): IO[AuthenticationError, WalletAdministrationContext] =
EntityAuthorizer.authorizeWalletAdmin(Entity.Default)

val layer = ZLayer.apply(ZIO.succeed(DefaultEntityAuthenticator))
}
Loading
Loading