Skip to content

Commit

Permalink
feat: support for ACLs in grpc endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
efgpinto committed Jan 24, 2025
1 parent f8d7219 commit 940c6e7
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 34 deletions.
6 changes: 3 additions & 3 deletions akka-javasdk-maven/akka-javasdk-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
<parent>
<groupId>io.akka</groupId>
<artifactId>akka-javasdk-maven</artifactId>
<version>3.0.2</version>
<version>3.0.2-109-f8d7219a-dev-SNAPSHOT</version>
</parent>

<groupId>io.akka</groupId>
<artifactId>akka-javasdk-parent</artifactId>
<version>3.0.2</version>
<version>3.0.2-109-f8d7219a-dev-SNAPSHOT</version>
<packaging>pom</packaging>


Expand Down Expand Up @@ -40,7 +40,7 @@

<!-- These are dependent on runtime environment and cannot be customized by users -->
<maven.compiler.release>21</maven.compiler.release>
<kalix-runtime.version>1.3.1-6cd7992</kalix-runtime.version>
<kalix-runtime.version>1.3.1</kalix-runtime.version>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skip.docker>false</skip.docker>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)}"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

}
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;
Expand All @@ -13,6 +14,7 @@

import java.util.concurrent.CompletionStage;

@Acl(allow = @Acl.Matcher(principal = Acl.Principal.ALL))
@GrpcEndpoint
public class CustomerGrpcEndpointImpl implements CustomerGrpcEndpoint {

Expand Down Expand Up @@ -57,6 +59,7 @@ public CompletionStage<ChangeNameResponse> changeName(ChangeNameRequest in) {
.thenApply(__ -> ChangeNameResponse.getDefaultInstance());
}

@Acl(deny = @Acl.Matcher(principal = Acl.Principal.ALL))
@Override
public CompletionStage<ChangeAddressResponse> changeAddress(ChangeAddressRequest in) {
log.info("gRPC request to change customer [{}] address: {}", in.getCustomerId(), in.getNewAddress());
Expand Down

0 comments on commit 940c6e7

Please sign in to comment.