From 940c6e73d58efd2902f97dd075cb7f48385eed46 Mon Sep 17 00:00:00 2001 From: Eduardo Pinto Date: Fri, 24 Jan 2025 11:30:37 +0000 Subject: [PATCH] feat: support for ACLs in grpc endpoints --- .../akka-javasdk-parent/pom.xml | 6 ++-- .../javasdk/impl/AclDescriptorFactory.scala | 28 +++++++++++++++++ .../impl/GrpcEndpointDescriptorFactory.scala | 27 ++++++++++++++++- .../impl/HttpEndpointDescriptorFactory.scala | 30 +------------------ project/Dependencies.scala | 2 +- .../api/CustomerGrpcEndpointImpl.java | 3 ++ 6 files changed, 62 insertions(+), 34 deletions(-) diff --git a/akka-javasdk-maven/akka-javasdk-parent/pom.xml b/akka-javasdk-maven/akka-javasdk-parent/pom.xml index 17ccf9451..fdab86fad 100644 --- a/akka-javasdk-maven/akka-javasdk-parent/pom.xml +++ b/akka-javasdk-maven/akka-javasdk-parent/pom.xml @@ -7,12 +7,12 @@ io.akka akka-javasdk-maven - 3.0.2 + 3.0.2-109-f8d7219a-dev-SNAPSHOT io.akka akka-javasdk-parent - 3.0.2 + 3.0.2-109-f8d7219a-dev-SNAPSHOT pom @@ -40,7 +40,7 @@ 21 - 1.3.1-6cd7992 + 1.3.1 UTF-8 false diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/AclDescriptorFactory.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/AclDescriptorFactory.scala index 8fda191cd..4897079fe 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/AclDescriptorFactory.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/AclDescriptorFactory.scala @@ -6,6 +6,11 @@ package akka.javasdk.impl import akka.annotation.InternalApi import akka.javasdk.annotations.Acl +import akka.runtime.sdk.spi.ACL +import akka.runtime.sdk.spi.All +import akka.runtime.sdk.spi.Internet +import akka.runtime.sdk.spi.PrincipalMatcher +import akka.runtime.sdk.spi.ServiceNamePattern /** * INTERNAL API @@ -22,4 +27,27 @@ private[impl] object AclDescriptorFactory { throw new IllegalArgumentException(invalidAnnotationUsage) } + // receives the method, checks if it is annotated with @Acl and if so, + // converts that into ACL spi object + def deriveAclOptions(aclAnnotation: Option[Acl]): Option[ACL] = + aclAnnotation.map { ann => + ann.allow().foreach(matcher => validateMatcher(matcher)) + ann.deny().foreach(matcher => validateMatcher(matcher)) + + new ACL( + allow = Option(ann.allow).map(toPrincipalMatcher).getOrElse(Nil), + deny = Option(ann.deny).map(toPrincipalMatcher).getOrElse(Nil), + denyHttpCode = None // FIXME we can probably use http codes instead of grpc ones + ) + } + + private def toPrincipalMatcher(matchers: Array[Acl.Matcher]): List[PrincipalMatcher] = + matchers.map { m => + m.principal match { + case Acl.Principal.ALL => All + case Acl.Principal.INTERNET => Internet + case Acl.Principal.UNSPECIFIED => new ServiceNamePattern(m.service()) + } + }.toList + } diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala index 88dc0c759..246533e9e 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/GrpcEndpointDescriptorFactory.scala @@ -9,13 +9,20 @@ import akka.grpc.ServiceDescription import akka.grpc.scaladsl.InstancePerRequestFactory import akka.http.scaladsl.model.HttpRequest import akka.http.scaladsl.model.HttpResponse +import akka.javasdk.annotations.Acl +import akka.javasdk.impl.AclDescriptorFactory.deriveAclOptions +import akka.javasdk.impl.ComponentDescriptorFactory.hasAcl +import akka.runtime.sdk.spi.ComponentOptions import akka.runtime.sdk.spi.GrpcEndpointDescriptor import akka.runtime.sdk.spi.GrpcEndpointRequestConstructionContext +import akka.runtime.sdk.spi.MethodOptions import scala.concurrent.Future object GrpcEndpointDescriptorFactory { + val logger: org.slf4j.Logger = org.slf4j.LoggerFactory.getLogger(GrpcEndpointDescriptorFactory.getClass) + def apply[T](grpcEndpointClass: Class[T], factory: () => T)(implicit system: ActorSystem[_]): GrpcEndpointDescriptor[T] = { // FIXME now way right now to know that it is a gRPC service interface @@ -54,12 +61,30 @@ object GrpcEndpointDescriptorFactory { system) } + val componentOptions = + new ComponentOptions(deriveAclOptions(Option(grpcEndpointClass.getAnnotation(classOf[Acl]))), None) + + val methodOptions: Map[String, MethodOptions] = grpcEndpointClass.getMethods + .filter(m => hasAcl(m)) + .map { m => + // FIXME just do to lower case and change runtime to handle it + capitalizeFirstLetter(m.getName) -> new MethodOptions( + deriveAclOptions(Option(m.getAnnotation(classOf[Acl]))), + None) + } + .toMap new GrpcEndpointDescriptor[T]( grpcEndpointClass.getName, description.name, description.descriptor, instanceFactory, - routeFactory) + routeFactory, + componentOptions, + methodOptions) + } + + def capitalizeFirstLetter(str: String): String = { + s"${str.charAt(0).toUpper}${str.substring(1)}" } } diff --git a/akka-javasdk/src/main/scala/akka/javasdk/impl/HttpEndpointDescriptorFactory.scala b/akka-javasdk/src/main/scala/akka/javasdk/impl/HttpEndpointDescriptorFactory.scala index 68d29e812..ec8356b1c 100644 --- a/akka-javasdk/src/main/scala/akka/javasdk/impl/HttpEndpointDescriptorFactory.scala +++ b/akka-javasdk/src/main/scala/akka/javasdk/impl/HttpEndpointDescriptorFactory.scala @@ -6,7 +6,6 @@ package akka.javasdk.impl import akka.http.scaladsl.model.HttpMethods import akka.javasdk.impl.reflection.Reflect -import AclDescriptorFactory.validateMatcher import akka.annotation.InternalApi import akka.http.scaladsl.model.Uri.Path import akka.javasdk.annotations.Acl @@ -18,21 +17,17 @@ import akka.javasdk.annotations.http.Post import akka.javasdk.annotations.http.Put import akka.javasdk.annotations.JWT import akka.javasdk.annotations.JWT.JwtMethodMode +import akka.javasdk.impl.AclDescriptorFactory.deriveAclOptions import akka.javasdk.impl.Validations.Invalid import akka.javasdk.impl.Validations.Validation -import akka.runtime.sdk.spi.ACL import akka.runtime.sdk.spi.{ JWT => RuntimeJWT } -import akka.runtime.sdk.spi.All import akka.runtime.sdk.spi.ClaimPattern import akka.runtime.sdk.spi.ClaimValues import akka.runtime.sdk.spi.ComponentOptions import akka.runtime.sdk.spi.HttpEndpointConstructionContext import akka.runtime.sdk.spi.HttpEndpointDescriptor import akka.runtime.sdk.spi.HttpEndpointMethodDescriptor -import akka.runtime.sdk.spi.Internet import akka.runtime.sdk.spi.MethodOptions -import akka.runtime.sdk.spi.PrincipalMatcher -import akka.runtime.sdk.spi.ServiceNamePattern import akka.runtime.sdk.spi.StaticClaim import akka.runtime.sdk.spi.StaticClaimContent import org.slf4j.LoggerFactory @@ -152,20 +147,6 @@ private[javasdk] object HttpEndpointDescriptorFactory { } } - // receives the method, checks if it is annotated with @Acl and if so, - // converts that into ACL spi object - private def deriveAclOptions(aclAnnotation: Option[Acl]): Option[ACL] = - aclAnnotation.map { ann => - ann.allow().foreach(matcher => validateMatcher(matcher)) - ann.deny().foreach(matcher => validateMatcher(matcher)) - - new ACL( - allow = Option(ann.allow).map(toPrincipalMatcher).getOrElse(Nil), - deny = Option(ann.deny).map(toPrincipalMatcher).getOrElse(Nil), - denyHttpCode = None // FIXME we can probably use http codes instead of grpc ones - ) - } - private[impl] def extractEnvVars(claimValueContent: String, claimRef: String): String = { val pattern = """\$\{([A-Z_][A-Z0-9_]*)\}""".r @@ -218,13 +199,4 @@ private[javasdk] object HttpEndpointDescriptorFactory { } } - private def toPrincipalMatcher(matchers: Array[Acl.Matcher]): List[PrincipalMatcher] = - matchers.map { m => - m.principal match { - case Acl.Principal.ALL => All - case Acl.Principal.INTERNET => Internet - case Acl.Principal.UNSPECIFIED => new ServiceNamePattern(m.service()) - } - }.toList - } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1ecb13a2e..8acc7a39e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { val ProtocolVersionMinor = 1 val RuntimeImage = "gcr.io/kalix-public/kalix-runtime" // Remember to bump kalix-runtime.version in akka-javasdk-maven/akka-javasdk-parent if bumping this - val RuntimeVersion = sys.props.getOrElse("kalix-runtime.version", "1.3.1-6cd7992") + val RuntimeVersion = sys.props.getOrElse("kalix-runtime.version", "1.3.1") } // NOTE: embedded SDK should have the AkkaVersion aligned, when updating RuntimeVersion, make sure to check // if AkkaVersion and AkkaHttpVersion are aligned diff --git a/samples/event-sourced-customer-registry/src/main/java/customer/api/CustomerGrpcEndpointImpl.java b/samples/event-sourced-customer-registry/src/main/java/customer/api/CustomerGrpcEndpointImpl.java index 8aec36d27..c54ef861e 100644 --- a/samples/event-sourced-customer-registry/src/main/java/customer/api/CustomerGrpcEndpointImpl.java +++ b/samples/event-sourced-customer-registry/src/main/java/customer/api/CustomerGrpcEndpointImpl.java @@ -1,6 +1,7 @@ package customer.api; import akka.grpc.GrpcServiceException; +import akka.javasdk.annotations.Acl; import akka.javasdk.annotations.GrpcEndpoint; import akka.javasdk.client.ComponentClient; import customer.api.proto.*; @@ -13,6 +14,7 @@ import java.util.concurrent.CompletionStage; +@Acl(allow = @Acl.Matcher(principal = Acl.Principal.ALL)) @GrpcEndpoint public class CustomerGrpcEndpointImpl implements CustomerGrpcEndpoint { @@ -57,6 +59,7 @@ public CompletionStage changeName(ChangeNameRequest in) { .thenApply(__ -> ChangeNameResponse.getDefaultInstance()); } + @Acl(deny = @Acl.Matcher(principal = Acl.Principal.ALL)) @Override public CompletionStage changeAddress(ChangeAddressRequest in) { log.info("gRPC request to change customer [{}] address: {}", in.getCustomerId(), in.getNewAddress());