Skip to content

Commit

Permalink
chore: initial commit (#1)
Browse files Browse the repository at this point in the history
* chore: Initial commit

* fix heading

* hide Sverre's old package names
  • Loading branch information
sverrehu authored Nov 7, 2023
1 parent 706b40f commit 2056cfd
Show file tree
Hide file tree
Showing 41 changed files with 2,174 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @statnett/k3a
4 changes: 4 additions & 0 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>statnett/renovate-config"],
}
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: CI
on:
pull_request: {}

permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- run: mvn --batch-mode verify
17 changes: 17 additions & 0 deletions .github/workflows/lint-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Lint PR
on:
pull_request_target:
types:
- opened
- edited
- synchronize

permissions:
contents: read
jobs:
trigger:
uses: statnett/github-workflows/.github/workflows/lint-pr.yaml@main
permissions:
pull-requests: write
statuses: write
19 changes: 19 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: Release Please
on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read
jobs:
trigger:
uses: statnett/github-workflows/.github/workflows/release-please.yaml@main
with:
release-type: maven
secrets: inherit
permissions:
contents: write
pull-requests: write
21 changes: 21 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Publish package
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Build package
run: mvn --batch-mode verify
- name: Push GitHub release artifact
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
with:
files: target/*.jar
20 changes: 20 additions & 0 deletions .github/workflows/scorecard.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Scorecard supply-chain security
on:
branch_protection_rule:
schedule:
- cron: "20 7 * * 2"
push:
branches:
- main

permissions:
contents: read
jobs:
trigger:
uses: statnett/github-workflows/.github/workflows/scorecard.yaml@main
permissions:
security-events: write
id-token: write
contents: read
actions: read
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea/
target
*.iml
.DS_Store
*~
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## 1.0.0 (2020-10-23)

* Initial, internal version.
138 changes: 137 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,138 @@
# k3a-ldap-authenticator
A Kafka AuthenticateCallbackHandler that supports LDAP/Active Directory for username/password verification, including authorization based on group membership.

This module contains:

* A [Kafka
`AuthenticateCallbackHandler`](src/main/java/io/statnett/k3a/authz/ldap/LdapAuthenticateCallbackHandler.java)
that uses a directory (LDAP/Active Directory) to verify a username
and a plain-text password.
* A [Kafka
`AclAuthorizer`](src/main/java/io/statnett/k3a/authz/ldap/LdapGroupAclAuthorizer.java)
and
[StandardAuthorizer](src/main/java/io/statnett/k3a/authz/ldap/LdapGroupStandardAuthorizer.java)
that know about principals of type `Group`, and check them against
LDAP/Active Directory group membership. The first is for
installations that use ZooKeeper ACLs, the second for installations
running without ZooKeepers.

If you do not care about group membership, you only need to set up the
first class. For group membership you need both, since the
`AclAuthorizer`/`StandardAuthorizer` builds on data fetched by the
`AuthenticateCallbackHandler`.

## Misc. notes

* Since all this is based on plain-text passwords, you will want to
run it over SSL/TLS.
* This is SASL_PLAIN, meaning that both the Kafka broker and the
client application will get access to the user's password before
passing it on to the directory server where the authentication is
taking place. In many environments this is considered unacceptable:
Look for something like OAUTH or SAML instead.
* Although group membership is cached, there is no caching of the
authentication result, but it is trivial to implement if needed. If
you do, please do not use the plain-text password as part of the
cache key, but pass it through a hashing function first.

## Configuration

Configuration is done using Kafka properties: Either
`dot.separated.properties`, or `KAFKA_ENVIRONMENT_VARIABLES`. To use
environment variables, capitalize every letter of the original
property, replace any underscores with a double underscore, replace
dots with underscores, and prefix with `KAFKA_`.

### Kafka integration

The `.jar`-file of this project must be made available on the Kafka
Broker classpath, typically in `/usr/share/java/kafka/`.

### Authenticator configuration

You will need a binding and a listener for either `SASL_SSL` or
`SASL_PLAINTEXT`. In the following we assume `SASL_SSL`, and a binding
named `HUMAN`:

```properties
advertised.listeners=... ,HUMAN://broker:9094
listener.security.protocol.map=... ,HUMAN:SASL_SSL
```

Tell Kafka to enable SASL, and to use our class to handle the protocol
binding created above:

```properties
listener.name.human.plain.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required ;
listener.name.human.plain.sasl.server.callback.handler.class=io.statnett.k3a.authz.ldap.LdapAuthenticateCallbackHandler
sasl.enabled.mechanisms=PLAIN
```

The authenticator plug-in expects some configuration parameters,
prefixed with `authz.ldap`, to tell it how to connect to and handle
the LDAP Directory:

```properties
authz.ldap.host=openldap
authz.ldap.port=389
authz.ldap.base.dn=dc=example,dc=com
authz.ldap.username.to.dn.format=cn=%s,ou=People,dc=example,dc=com
```

LDAPS (TLS) is assumed if the port is 636. For all other ports,
plain-text LDAP is assumed. If using LDAPS with a self-signed
certificate, the Broker JVM must be told to trust your
certificates. How to do that is beyond the scope of this README.

The final parameter, `ldap.username.to.dn.format`, specifies how the
incoming username should be transformed to match whatever the
directory expects as part of a bind operation. The `%s` combination
will be replaced by a properly escaped version of what the user
provided. On Active Directory this string should often be specified as
just `%s`, since the directory authenticates using just the username
without matching a full DN.

### Group authorizer configuration

If you want to enable ACLs which contain a `Group` principal type, you
will need the above configuration, plus our `LdapGroupAclAuthorizer`
(for ZooKeeper ACLs) or `LdapGroupStandardAuthorizer` for KRaft mode
ACLs.

Requirements for our group authorizer:

* Authorization is done based on the automatically populated
`memberOf` attribute that was introduced in draft [RFC
2307bis](https://tools.ietf.org/id/draft-howard-rfc2307bis-01.txt).
Although a draft RFC, this was adopted by Microsoft in Active
Directory. Later, the same functionality has become available in
other directories as well.
* The Group authorizer does not use a distinct user for LDAP searches,
but instead assumes that every user is allowed to search the entire
LDAP tree to find itself. This is not the case everywhere.

Tell Kafka to use this authorizer like this:

```properties
authorizer.class.name=io.statnett.k3a.authz.ldap.LdapGroupAclAuthorizer
```

or this:

```properties
authorizer.class.name=io.statnett.k3a.authz.ldap.LdapGroupStandardAuthorizer
```

The authorizer will perform a search in the entire LDAP tree to find
the attributes of the authenticated user. The following attribute
tells it how to map a username to a search filter:

```properties
authz.ldap.username.to.unique.search.format=uid=%s
```

Group principals, for use in ACLs, must contain the full DN of the
group:

```text
Group:cn=producers,ou=Groups,dc=example,dc=com
```
130 changes: 130 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.statnett.k3a</groupId>
<artifactId>k3a-ldap-authenticator</artifactId>
<version>3.2.3-SNAPSHOT</version>
<packaging>jar</packaging>

<name>${project.groupId}:${project.artifactId}</name>
<description>A Kafka AuthenticateCallbackHandler that supports LDAP/Active Directory for username/password verification, including authorization based on group membership.</description>
<url>https://github.com/statnett/k3a-ldap-authenticator</url>

<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/license/mit/</url>
</license>
</licenses>

<scm>
<connection>scm:git:git://github.com/statnett/k3a-ldap-authenticator.git</connection>
<developerConnection>scm:git:ssh://github.com:statnett/k3a-ldap-authenticator.git</developerConnection>
<url>https://github.com/statnett/k3a-ldap-authenticator/tree/main</url>
</scm>

<properties>
<java.version>1.8</java.version>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kafka.version>3.6.0</kafka.version>
<kafka.scala.version>2.13</kafka.scala.version>
</properties>

<dependencies>

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>${kafka.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_${kafka.scala.version}</artifactId>
<version>${kafka.version}</version>
<scope>provided</scope>
</dependency>

<!-- Required by Apache version of kafka-clients -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.36</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>no.shhsoft</groupId>
<artifactId>k3a-embedded</artifactId>
<version>0.5.0+${kafka.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>6.0.10</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.zapodot</groupId>
<artifactId>embedded-ldap-junit</artifactId>
<version>0.9.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Loading

0 comments on commit 2056cfd

Please sign in to comment.