kafka 2.4 的release note 除了引入MirrorMaker 2.0 之外,还有一项改动是引入了新的java版的认证API接口。原因是这样的:
kafka使用scla版的kafka.security.auth.Authorizer的trait来支持可插拔的授权接口。 但是KIP-50中已经同意用“client”模块中的org.apache.kafka.common包中的Java接口和Java ACL类来替换了它,但是之前一直都没有合并。于是社区就就添加了新版的java 授权接口, 这些Java接口能提供比Scala Trait更好的兼容性,能让开发者更容易开发自己的认证模块。
新版接口的工作流程是这样的:
如果在配置中实现了Reconfigurable这个类的话还可以动态变更Authorizer 的实现类而不用重启broker,还有三点需要注意的是:
除了上面提到的那些方法,还有下面三个方法
createAcls()和deleteAcls():用来添加和删除Acl
acls():用来获取符合查询条件的Acl
@InterfaceStability.Evolving
public interface Authorizer extends Configurable, Closeable {
Map<Endpoint, ? extends CompletionStage<Void>> start(AuthorizerServerInfo serverInfo);
List<AuthorizationResult> authorize(AuthorizableRequestContext requestContext, List<Action> actions);
List<? extends CompletionStage<AclCreateResult>> createAcls(AuthorizableRequestContext requestContext, List<AclBinding> aclBindings);
List<? extends CompletionStage<AclDeleteResult>> deleteAcls(AuthorizableRequestContext requestContext, List<AclBindingFilter> aclBindingFilters);
Iterable<AclBinding> acls(AclBindingFilter filter);
}
kafka2.4中同样提供了一个"SimpleAclAuthorizer"的实现,只不过这次它去掉了Simple,就叫"AclAuthorizer",功能和SimpleAclAuthorizer基本一样,只是实现方式变了,可以看下具体的方法:
好像什么也没干
override def start(serverInfo: AuthorizerServerInfo): util.Map[Endpoint, _ <: CompletionStage[Void]] = {
serverInfo.endpoints.asScala.map { endpoint =>
endpoint -> CompletableFuture.completedFuture[Void](null) }.toMap.asJava
}
在configure方法里读取了kafka的配置,生成了zk的客户端和aclChangeListeners(用来感知zk节点变化,若有变化,则更新acl的cache),并通过loadCache()加载了zk中已经有 的权限。
override def configure(javaConfigs: util.Map[String, _]): Unit = {
val configs = javaConfigs.asScala
val props = new java.util.Properties()
configs.foreach { case (key, value) => props.put(key, value.toString) }
superUsers = configs.get(AclAuthorizer.SuperUsersProp).collect {
case str: String if str.nonEmpty => str.split(";").map(s => SecurityUtils.parseKafkaPrincipal(s.trim)).toSet
}.getOrElse(Set.empty[KafkaPrincipal])
shouldAllowEveryoneIfNoAclIsFound = configs.get(AclAuthorizer.AllowEveryoneIfNoAclIsFoundProp).exists(_.toString.toBoolean)
// Use `KafkaConfig` in order to get the default ZK config values if not present in `javaConfigs`. Note that this
// means that `KafkaConfig.zkConnect` must always be set by the user (even if `AclAuthorizer.ZkUrlProp` is also
// set).
val kafkaConfig = KafkaConfig.fromProps(props, doLog = false)
val zkUrl = configs.get(AclAuthorizer.ZkUrlProp).map(_.toString).getOrElse(kafkaConfig.zkConnect)
val zkConnectionTimeoutMs = configs.get(AclAuthorizer.ZkConnectionTimeOutProp).map(_.toString.toInt).getOrElse(kafkaConfig.zkConnectionTimeoutMs)
val zkSessionTimeOutMs = configs.get(AclAuthorizer.ZkSessionTimeOutProp).map(_.toString.toInt).getOrElse(kafkaConfig.zkSessionTimeoutMs)
val zkMaxInFlightRequests = configs.get(AclAuthorizer.ZkMaxInFlightRequests).map(_.toString.toInt).getOrElse(kafkaConfig.zkMaxInFlightRequests)
val zkClientConfig = AclAuthorizer.zkClientConfigFromKafkaConfigAndMap(kafkaConfig, configs)
val time = Time.SYSTEM
zkClient = KafkaZkClient(zkUrl, kafkaConfig.zkEnableSecureAcls, zkSessionTimeOutMs, zkConnectionTimeoutMs,
zkMaxInFlightRequests, time, "kafka.security", "AclAuthorizer", name=Some("ACL authorizer"),
zkClientConfig = zkClientConfig)
zkClient.createAclPaths()
extendedAclSupport = kafkaConfig.interBrokerProtocolVersion >= KAFKA_2_0_IV1
// Start change listeners first and then populate the cache so that there is no timing window
// between loading cache and processing change notifications.
startZkChangeListeners()
loadCache()
}
authorize方法应该是直接调用了authorizeAction(),在这个方法里Authorizer从AuthorizableRequestContext中获取了请求的principal和host;从Action中获取了resource和operation。
鉴权流程是这样的:
private def authorizeAction(requestContext: AuthorizableRequestContext, action: Action): AuthorizationResult = {
val resource = action.resourcePattern
if (resource.patternType != PatternType.LITERAL) {
throw new IllegalArgumentException("Only literal resources are supported. Got: " + resource.patternType)
}
// ensure we compare identical classes
val sessionPrincipal = requestContext.principal
val principal = if (classOf[KafkaPrincipal] != sessionPrincipal.getClass)
new KafkaPrincipal(sessionPrincipal.getPrincipalType, sessionPrincipal.getName)
else
sessionPrincipal
val host = requestContext.clientAddress.getHostAddress
val operation = action.operation
def isEmptyAclAndAuthorized(acls: Set[AclEntry]): Boolean = {
if (acls.isEmpty) {
// No ACLs found for this resource, permission is determined by value of config allow.everyone.if.no.acl.found
authorizerLogger.debug(s"No acl found for resource $resource, authorized = $shouldAllowEveryoneIfNoAclIsFound")
shouldAllowEveryoneIfNoAclIsFound
} else false
}
def denyAclExists(acls: Set[AclEntry]): Boolean = {
// Check if there are any Deny ACLs which would forbid this operation.
matchingAclExists(operation, resource, principal, host, DENY, acls)
}
def allowAclExists(acls: Set[AclEntry]): Boolean = {
// Check if there are any Allow ACLs which would allow this operation.
// Allowing read, write, delete, or alter implies allowing describe.
// See #{org.apache.kafka.common.acl.AclOperation} for more details about ACL inheritance.
val allowOps = operation match {
case DESCRIBE => Set[AclOperation](DESCRIBE, READ, WRITE, DELETE, ALTER)
case DESCRIBE_CONFIGS => Set[AclOperation](DESCRIBE_CONFIGS, ALTER_CONFIGS)
case _ => Set[AclOperation](operation)
}
allowOps.exists(operation => matchingAclExists(operation, resource, principal, host, ALLOW, acls))
}
def aclsAllowAccess = {
//we allow an operation if no acls are found and user has configured to allow all users
//when no acls are found or if no deny acls are found and at least one allow acls matches.
val acls = matchingAcls(resource.resourceType, resource.name)
isEmptyAclAndAuthorized(acls) || (!denyAclExists(acls) && allowAclExists(acls))
}
// Evaluate if operation is allowed
val authorized = isSuperUser(principal) || aclsAllowAccess
logAuditMessage(requestContext, action, authorized)
if (authorized) AuthorizationResult.ALLOWED else AuthorizationResult.DENIED
}
先根据传入的List