Skip to content

Commit e259a61

Browse files
adwk67sbernauersiegfriedweber
authored
feat: add Kerberos authentication for Kafka (#762)
* added enum for either vector of autentication classes or kerberos secret name * initial kerberos impl * implement kerberos specifics * wip: integration test * revert complex enum and use parallel struct (CRD decision pending) * call shell explicitly for kerberos probe to allow variable substitution * working test * revert class name change and formatting * added validation * linting * more linting * refactor: add kerberos to list of authentication classes instead of dedicated struct * changelog * reverted operator-rs ref and corrected test * fixed changes due to operator-rs to 0.78.0 * added docs/example * improved comments * Update rust/crd/src/authentication.rs Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * Update rust/crd/src/lib.rs Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * Update rust/operator-binary/src/kafka_controller.rs Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * Update rust/operator-binary/src/kafka_controller.rs Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * fixed review suggestions * formatting: new lines between enum elements * review suggestions * add use-client-tls dimension and cleanup test * add constants for kerberos paths * test: Update kerberos tests to always use TLS * added check that TLS is enabled for Kerberos * regenerate charts * formatting * corrected validation check * Update rust/operator-binary/src/kerberos.rs Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * use listener volume scope for kerberos volume and replace FQDN with listener in advertised listeners * added custom iamge usage from previous merge from main * Update rust/crd/src/authentication.rs Co-authored-by: Siegfried Weber <mail@siegfriedweber.net> * remove unecessary test * removed unused Error * regenerate charts * combine cases where internal tls is required * working test with broker listeners instead of listener bootstrap * add listener for bootstrapper * removed duplicate check * add bootstrap configs for client_auth as well * use correct port in discovery for kerberos. Removed bootstrap changes for non-kerberos. * use discovery in kerberos test * added unit test for kerberos config * added note about client connections and ports * Update docs/modules/kafka/pages/usage-guide/security.adoc Co-authored-by: Siegfried Weber <mail@siegfriedweber.net> * clarified comment --------- Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.tech> Co-authored-by: Siegfried Weber <mail@siegfriedweber.net>
1 parent f82d8d8 commit e259a61

26 files changed

+1155
-74
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
77
### Added
88

99
- Support version `3.8.0` ([#753]).
10+
- Add support for Kerberos authentication ([#762]).
1011
- The operator can now run on Kubernetes clusters using a non-default cluster domain.
1112
Use the env var `KUBERNETES_CLUSTER_DOMAIN` or the operator Helm chart property `kubernetesClusterDomain` to set a non-default cluster domain ([#771]).
1213

@@ -35,6 +36,7 @@ All notable changes to this project will be documented in this file.
3536
[#741]: https://github.com/stackabletech/kafka-operator/pull/741
3637
[#750]: https://github.com/stackabletech/kafka-operator/pull/750
3738
[#753]: https://github.com/stackabletech/kafka-operator/pull/753
39+
[#762]: https://github.com/stackabletech/kafka-operator/pull/762
3840
[#771]: https://github.com/stackabletech/kafka-operator/pull/771
3941
[#773]: https://github.com/stackabletech/kafka-operator/pull/773
4042

deploy/helm/kafka-operator/crds/crds.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,10 @@ spec:
565565
Only affects client connections. This setting controls: - If clients need to authenticate themselves against the broker via TLS - Which ca.crt to use when validating the provided client certs
566566
567567
This will override the server TLS settings (if set) in `spec.clusterConfig.tls.serverSecretClass`.
568+
569+
## Kerberos provider
570+
571+
This affects client connections and also requires TLS for encryption. This setting is used to reference an `AuthenticationClass` and in turn, a `SecretClass` that is used to create keytabs.
568572
type: string
569573
required:
570574
- authenticationClass

docs/modules/kafka/pages/usage-guide/security.adoc

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ You can create your own secrets and reference them e.g. in the `spec.clusterConf
5353
== Authentication
5454

5555
The internal or broker-to-broker communication is authenticated via TLS.
56+
For client-to-server communication, authentication can be achieved with either TLS or Kerberos.
57+
58+
=== TLS
59+
5660
In order to enforce TLS authentication for client-to-server communication, you can set an `AuthenticationClass` reference in the custom resource provided by the xref:commons-operator:index.adoc[Commons Operator].
5761

5862
[source,yaml]
@@ -101,6 +105,65 @@ spec:
101105
<3> The reference to a `SecretClass`.
102106
<4> The `SecretClass` that is referenced by the `AuthenticationClass` in order to provide certificates.
103107

108+
=== Kerberos
109+
110+
Similarly, you can set an `AuthenticationClass` reference for a Kerberos authentication provider:
111+
112+
[source,yaml]
113+
----
114+
apiVersion: authentication.stackable.tech/v1alpha1
115+
kind: AuthenticationClass
116+
metadata:
117+
name: kafka-client-kerberos # <2>
118+
spec:
119+
provider:
120+
kerberos:
121+
kerberosSecretClass: kafka-client-auth-secret # <3>
122+
---
123+
apiVersion: secrets.stackable.tech/v1alpha1
124+
kind: SecretClass
125+
metadata:
126+
name: kafka-client-auth-secret # <4>
127+
spec:
128+
backend:
129+
kerberosKeytab:
130+
...
131+
---
132+
apiVersion: kafka.stackable.tech/v1alpha1
133+
kind: KafkaCluster
134+
metadata:
135+
name: simple-kafka
136+
spec:
137+
image:
138+
productVersion: 3.7.1
139+
clusterConfig:
140+
authentication:
141+
- authenticationClass: kafka-client-kerberos # <1>
142+
tls:
143+
serverSecretClass: tls # <5>
144+
zookeeperConfigMapName: simple-kafka-znode
145+
brokers:
146+
roleGroups:
147+
default:
148+
replicas: 3
149+
----
150+
<1> The `clusterConfig.authentication.authenticationClass` can be set to use Kerberos for authentication. This is optional.
151+
<2> The referenced `AuthenticationClass` that references a `SecretClass` to provide Kerberos keytabs.
152+
<3> The reference to a `SecretClass`.
153+
<4> The `SecretClass` that is referenced by the `AuthenticationClass` in order to provide keytabs.
154+
<5> The SecretClass that will be used for encryption.
155+
156+
NOTE: When Kerberos is enabled it is also required to enable TLS for maximum security.
157+
158+
==== Clients
159+
160+
In order to keep client configuration as uncluttered as possible, each kerberized Kafka broker has two principals: one for the broker itself and one for the bootstrap service.
161+
The client can connect to the bootstrap service, which returns the broker quorum for use in subsequent operations.
162+
This is transparent as each connection dynamically uses the relevant principal (broker or bootstrap).
163+
In order for this to work, it is necessary for kerberized clusters to define an extra Kafka listener for the bootstrap with a corresponding service (and port).
164+
The bootstrap address is written to the discovery ConfigMap, using the Stackable bootstrap listener with the port being 9095 (secure) for kerberized clusters, and 9092 (non-secure) or 9093 (secure) for non-kerberized ones.
165+
166+
NOTE: Port 9094 is reserved for non-secure kerberized connections which is not currently implemented.
104167

105168
== [[authorization]]Authorization
106169

rust/crd/src/authentication.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use stackable_operator::{
99
schemars::{self, JsonSchema},
1010
};
1111

12-
const SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS: [&str; 1] = ["TLS"];
12+
pub const SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS: [&str; 2] = ["TLS", "Kerberos"];
1313

1414
#[derive(Snafu, Debug)]
1515
pub enum Error {
@@ -18,9 +18,10 @@ pub enum Error {
1818
source: stackable_operator::client::Error,
1919
authentication_class: ObjectRef<AuthenticationClass>,
2020
},
21-
// TODO: Adapt message if multiple authentication classes are supported
22-
#[snafu(display("only one authentication class is currently supported. Possible Authentication class providers are {SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS:?}"))]
21+
22+
#[snafu(display("only one authentication class at a time is currently supported. Possible Authentication class providers are {SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS:?}"))]
2323
MultipleAuthenticationClassesProvided,
24+
2425
#[snafu(display(
2526
"failed to use authentication provider [{provider}] for authentication class [{authentication_class}] - supported providers: {SUPPORTED_AUTHENTICATION_CLASS_PROVIDERS:?}",
2627
))]
@@ -42,6 +43,12 @@ pub struct KafkaAuthentication {
4243
/// - Which ca.crt to use when validating the provided client certs
4344
///
4445
/// This will override the server TLS settings (if set) in `spec.clusterConfig.tls.serverSecretClass`.
46+
///
47+
/// ## Kerberos provider
48+
///
49+
/// This affects client connections and also requires TLS for encryption.
50+
/// This setting is used to reference an `AuthenticationClass` and in turn, a `SecretClass` that is
51+
/// used to create keytabs.
4552
pub authentication_class: String,
4653
}
4754

@@ -90,6 +97,13 @@ impl ResolvedAuthenticationClasses {
9097
.find(|auth| matches!(auth.spec.provider, AuthenticationClassProvider::Tls(_)))
9198
}
9299

100+
/// Return the (first) Kerberos `AuthenticationClass` if available
101+
pub fn get_kerberos_authentication_class(&self) -> Option<&AuthenticationClass> {
102+
self.resolved_authentication_classes
103+
.iter()
104+
.find(|auth| matches!(auth.spec.provider, AuthenticationClassProvider::Kerberos(_)))
105+
}
106+
93107
/// Validates the resolved AuthenticationClasses.
94108
/// Currently errors out if:
95109
/// - More than one AuthenticationClass was provided
@@ -101,8 +115,11 @@ impl ResolvedAuthenticationClasses {
101115

102116
for auth_class in &self.resolved_authentication_classes {
103117
match &auth_class.spec.provider {
104-
AuthenticationClassProvider::Tls(_) => {}
105-
_ => {
118+
// explicitly list each branch so new elements do not get overlooked
119+
AuthenticationClassProvider::Tls(_) | AuthenticationClassProvider::Kerberos(_) => {}
120+
AuthenticationClassProvider::Static(_)
121+
| AuthenticationClassProvider::Ldap(_)
122+
| AuthenticationClassProvider::Oidc(_) => {
106123
return Err(Error::AuthenticationProviderNotSupported {
107124
authentication_class: ObjectRef::from_obj(auth_class),
108125
provider: auth_class.spec.provider.to_string(),

rust/crd/src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ pub mod listener;
55
pub mod security;
66
pub mod tls;
77

8-
use crate::authentication::KafkaAuthentication;
98
use crate::authorization::KafkaAuthorization;
109
use crate::tls::KafkaTls;
1110

1211
use affinity::get_affinity;
12+
use authentication::KafkaAuthentication;
1313
use serde::{Deserialize, Serialize};
1414
use snafu::{OptionExt, ResultExt, Snafu};
1515
use stackable_operator::{
@@ -63,6 +63,9 @@ pub const STACKABLE_DATA_DIR: &str = "/stackable/data";
6363
pub const STACKABLE_CONFIG_DIR: &str = "/stackable/config";
6464
pub const STACKABLE_LOG_CONFIG_DIR: &str = "/stackable/log_config";
6565
pub const STACKABLE_LOG_DIR: &str = "/stackable/log";
66+
// kerberos
67+
pub const STACKABLE_KERBEROS_DIR: &str = "/stackable/kerberos";
68+
pub const STACKABLE_KERBEROS_KRB5_PATH: &str = "/stackable/kerberos/krb5.conf";
6669

6770
const DEFAULT_BROKER_GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_minutes_unchecked(30);
6871

@@ -335,6 +338,13 @@ impl KafkaRole {
335338
}
336339
roles
337340
}
341+
342+
/// A Kerberos principal has three parts, with the form username/fully.qualified.domain.name@YOUR-REALM.COM.
343+
/// We only have one role and will use "kafka" everywhere (which e.g. differs from the current hdfs implementation,
344+
/// but is similar to HBase).
345+
pub fn kerberos_service_name(&self) -> &'static str {
346+
"kafka"
347+
}
338348
}
339349

340350
#[derive(Clone, Debug, Default, PartialEq, Fragment, JsonSchema)]

0 commit comments

Comments
 (0)