Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisseto committed Apr 12, 2024
1 parent 8ceb2aa commit b71d0fe
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 64 deletions.
111 changes: 111 additions & 0 deletions charts/redpanda/configmap.tpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,114 @@ func RedpandaAdditionalStartFlags(dot *helmette.Dot, smp, memory, reserveMemory

return append(flags, values.Statefulset.AdditionalRedpandaCmdFlags...)
}

// func RedpandaYAMLKafkaListeners(dot *helmette.Dot) []KafkaListener {
// values := helmette.Unwrap[Values](dot.Values)
//
// input := values.Listeners.Kafka
//
// if input.AuthenticationMethod == "" {
// input.AuthenticationMethod = "sasl" // ??
// // {{- if or (include "sasl-enabled" $root | fromJson).bool $kafkaService.authenticationMethod }}
// // authentication_method: {{ default "sasl" $kafkaService.authenticationMethod }}
// // {{- end }}
// }
//
// internalCert, ok := values.TLS.Certs[input.TLS.Cert]
// if !ok {
// panic(fmt.Sprintf("referenced certificate not defined: %q", input.TLS.Cert))
// }
//
// listeners := []KafkaListener{
// {
// Name: "internal",
// Address: "0.0.0.0",
// Port: values.Listeners.Kafka.Port,
// AuthenticationMethod: &values.Listeners.Kafka.AuthenticationMethod,
// TLS: KafkaListenerTLS{
// Enabled: false,
// CertFile: fmt.Sprintf("/etc/tls/certs/%s/tls.crt", input.TLS.Cert),
// KeyFile: fmt.Sprintf("/etc/tls/certs/%s/tls.key", input.TLS.Cert),
// RequireClientAuth: *input.TLS.RequireClientAuth,
// },
// },
// }
//
// // This is a required field so we use the default in the redpanda debian container.
// defaultTrustStore := "/etc/ssl/certs/ca-certificates.crt"
//
// if internalCert.CAEnabled {
// listeners[0].TLS.TrustStoreFile = fmt.Sprintf("/etc/tls/certs/%s/ca.crt", input.TLS.Cert)
// } else {
// listeners[0].TLS.TrustStoreFile = defaultTrustStore
// }
//
// names := helmette.Keys(input.External)
// helmette.SortAlpha(names)
//
// for _, name := range names {
// listener := input.External[name]
//
// var tls KafkaListenerTLS
// if listener.TLS != nil && listener.TLS.Cert != "" {
// cert, ok := values.TLS.Certs[listener.TLS.Cert]
// if !ok {
// panic("todo")
// }
//
// tls = KafkaListenerTLS{
// Enabled: *listener.Enabled,
// CertFile: fmt.Sprintf("/etc/tls/certs/%s/tls.crt", listener.TLS.Cert),
// KeyFile: fmt.Sprintf("/etc/tls/certs/%s/tls.key", listener.TLS.Cert),
// RequireClientAuth: *listener.TLS.RequireClientAuth,
// }
//
// if cert.CAEnabled {
// tls.TrustStoreFile = fmt.Sprintf("/etc/tls/certs/%s/ca.crt", listener.TLS.Cert)
// }
// }
//
// listeners = append(listeners, KafkaListener{
// Name: name,
// Address: "0.0.0.0",
// Port: listener.Port,
// // AdvertisedAddress: ,
// TLS: tls,
// })
// }
//
// return listeners
// }
//
// func RedpandaYAMLListenersKafkaAPI(dot *helmette.Dot) []map[string]any {
// kafkaListeners := RedpandaYAMLKafkaListeners(dot)
//
// var kafka_api []map[string]any
// for _, listener := range kafkaListeners {
// kafka_api = append(kafka_api, map[string]any{
// "name": listener.Name,
// "address": listener.Address,
// "port": listener.Port,
// "authentication_method": listener.AuthenticationMethod,
// })
// }
//
// return kafka_api
// }
//
// func RedpandaYAMLListenersKafkaAPITLS(dot *helmette.Dot) []map[string]any {
// kafkaListeners := RedpandaYAMLKafkaListeners(dot)
//
// var kafka_api_tls []map[string]any
// for _, listener := range kafkaListeners {
// kafka_api_tls = append(kafka_api_tls, map[string]any{
// "name": listener.Name,
// "enabled": listener.TLS.Enabled,
// "cert_file": listener.TLS.CertFile,
// "key_file": listener.TLS.KeyFile,
// "trust_store_file": listener.TLS.TrustStoreFile,
// })
// }
//
// return kafka_api_tls
// }
95 changes: 95 additions & 0 deletions charts/redpanda/kafka_listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package redpanda

// Commenting conventions:
// {GoFieldName} User facing documentation
// ---
// Developer facing documentation
// Annotations +kubebuilder,+gotohelm,etc

type BrokerConfig struct {
// KafkaListeners is an amalgam of `kafka_api`, `kafka_api_tls`,
// `advertised_kafka_api`. That may be configured in a more ergonomic and
// less brittle fashion. `{,advertised_}kafka_api{,_tls}` may be set
// directly but are mutually exclusive to KafkaListeners.
KafkaListeners []KafkaListener `json:"kafka_listeners,omitempty"`
// KafkaAPI is a direct mapping to `kafka_api`. It takes precedence over
// everything else, if provided. It is recommend to instead use .KafkaListeners.
// +verbatim
KafkaAPI []map[string]any `json:"kafka_api,omitempty"`
// AdvertisedKafkaAPI is a direct mapping to `advertised_kafka_api`. It
// takes precedence over everything else, if provided. It is recommend to
// instead use .KafkaListeners.
// +verbatim
AdvertisedKafkaAPI []map[string]any `json:"advertised_kafka_api,omitempty"`
// KafkaAPITLS is a direct mapping to `kafka_api_tls`. It takes precedence
// over everything else, if provided. It is recommend to instead use
// .KafkaListeners.
// +verbatim
KafkaAPITLS []map[string]any `json:"kafka_api_tls,omitempty"`
}

// ---
// Enum definition: https://github.com/redpanda-data/redpanda/blob/8d5d1cc6d56d77b575f69150140aa5689bb75c47/src/v/config/broker_authn_endpoint.h#L30
// +kubebuilder:validation:Enum=none;sasl;mtls_identity
type KafkaAuthenticationMethod string

// See also: https://docs.redpanda.com/current/manage/security/listener-configuration/
type KafkaListener struct {
// Name maps to the `kafka_api[*].name` field.
// Must be unique across all kafka listeners.
// +kubebuilder:validation:MaxLength=15
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
// Port maps to the `kafka_api[*].port` field.
// Must be unique across all listeners.
// +kubebuilder:validation:Minimum=1
Port int32 `json:"port"`
// Address maps to the `kafka_api[*].address` field.
// Generally doesn't need to be set.
// +kubebuilder:default="0.0.0.0"
Address *string `json:"address"`
// AuthenticationMethod maps to the `kafka_api[*].authentication_method`
// field.
// +kubebuilder:default=none
AuthenticationMethod KafkaAuthenticationMethod
// TLS, if set, configures the corresponding `kafka_api_tls` element for this
// `kafka_api` element.
// TLS *KafkaListenerTLS
TLS *KafkaListenerTLS
// AdvertisedAddress, if set, maps to the `advertised_kafka_api[*].address`
// field.
AdvertisedAddress *PerBrokerValue[string]
// AdvertisedPort, if set, maps to the `advertised_kafka_api[*].port`
// field.
AdvertisedPort *PerBrokerValue[int32]
}

type KafkaListenerTLS struct {
Config *KafkaListenerTLSConfig
TLSCertRef *TLSCertReference
// TODO Nice to have, just provide a reference to a cert-manager reference
// and we'll configure it for you.
// CertificateRef *CertificateReference
}

// See also https://docs.redpanda.com/current/manage/security/encryption/#configure-tls
type KafkaListenerTLSConfig struct {
// Enabled controls the `kafka_api_tls[*].enabled` field. It is passed
// through verbatim and does not affect any other configuration values.
// +kubebuilder:default=true
// +verbatim
Enabled *bool

// KeyFile maps to the `kafka_api_tls[*].key_file` field.
KeyFile FileSource

// CertFile maps to the `kafka_api_tls[*].cert_file` field.
CertFile FileSource

// TrustStoreFile maps to the `kafka_api_tls[*].truststore_file` field.
// +kubebuilder:default={"path": "/etc/ssl/certs/ca-certificates.crt"}
TrustStoreFile *FileSource

// RequireClientAuth maps to the `kafka_api_tls[*].required_client_auth` field.
RequireClientAuth bool `json:"required_client_auth"`
}
62 changes: 2 additions & 60 deletions charts/redpanda/templates/_configmap.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -283,66 +283,8 @@ redpanda.yaml: |
{{- end }}
{{- end -}}
{{/* Kafka API */}}
{{- $kafkaService := .Values.listeners.kafka }}
kafka_api:
- name: internal
address: 0.0.0.0
port: {{ $kafkaService.port }}
{{- if or (include "sasl-enabled" $root | fromJson).bool $kafkaService.authenticationMethod }}
authentication_method: {{ default "sasl" $kafkaService.authenticationMethod }}
{{- end }}
{{- range $name, $listener := $kafkaService.external }}
{{- if and $listener.port $name (dig "enabled" true $listener) }}
- name: {{ $name }}
address: 0.0.0.0
port: {{ $listener.port }}
{{- if or (include "sasl-enabled" $root | fromJson).bool $listener.authenticationMethod }}
authentication_method: {{ default "sasl" $listener.authenticationMethod }}
{{- end }}
{{- end }}
{{- end }}
kafka_api_tls:
{{- if (include "kafka-internal-tls-enabled" . | fromJson).bool }}
- name: internal
enabled: true
cert_file: /etc/tls/certs/{{ $kafkaService.tls.cert }}/tls.crt
key_file: /etc/tls/certs/{{ $kafkaService.tls.cert }}/tls.key
require_client_auth: {{ $kafkaService.tls.requireClientAuth }}
{{- $cert := get .Values.tls.certs $kafkaService.tls.cert }}
{{- if empty $cert }}
{{- fail (printf "Certificate used but not defined")}}
{{- end }}
{{- if $cert.caEnabled }}
truststore_file: /etc/tls/certs/{{ $kafkaService.tls.cert }}/ca.crt
{{- else }}
{{/* This is a required field so we use the default in the redpanda debian container */}}
truststore_file: /etc/ssl/certs/ca-certificates.crt
{{- end }}
{{- end }}
{{- range $name, $listener := $kafkaService.external }}
{{- $k := dict "Values" $values "listener" $listener }}
{{- if and (include "kafka-external-tls-enabled" $k | fromJson).bool (dig "enabled" true $listener) }}
{{- $mtls := dig "tls" "requireClientAuth" false $listener }}
{{- $mtls = dig "tls" "requireClientAuth" $mtls $k }}
{{- $certName := include "kafka-external-tls-cert" $k }}
{{- $certPath := printf "/etc/tls/certs/%s" $certName }}
{{- $cert := get $values.tls.certs $certName }}
{{- if empty $cert }}
{{- fail (printf "Certificate, '%s', used but not defined" $certName)}}
{{- end }}
- name: {{ $name }}
enabled: true
cert_file: {{ $certPath }}/tls.crt
key_file: {{ $certPath }}/tls.key
require_client_auth: {{ $mtls }}
{{- if $cert.caEnabled }}
truststore_file: {{ $certPath }}/ca.crt
{{- else }}
{{/* This is a required field so we use the default in the redpanda debian container */}}
truststore_file: /etc/ssl/certs/ca-certificates.crt
{{- end }}
{{- end }}
{{- end -}}
kafka_api: {{ (include "redpanda.RedpandaYAMLKafkaAPIListeners" ) }}
kafka_api_tls: {{ (include "redpanda.RedpandaYAMLKafkaAPITLSListeners" ) }}
{{/* RPC Server */}}
{{- $service = .Values.listeners.rpc }}
rpc_server:
Expand Down
15 changes: 12 additions & 3 deletions charts/redpanda/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import (
// the Values struct as well to ensure that nothing can ever get out of sync.

type Values struct {
// ---
// RedpandaYAML represents redpanda.yaml as is read by redpanda. Provide
// direct access to every field. Enhance when possible.
RedpandaYAML `json:",inline"`

NameOverride string `json:"nameOverride"`
FullnameOverride string `json:"fullnameOverride"`
ClusterDomain string `json:"clusterDomain"`
Expand Down Expand Up @@ -54,6 +59,10 @@ type Values struct {
} `json:"tests"`
}

type RedpandaYAML struct {
Redpanda *BrokerConfig `json:"redpanda"`
}

func (Values) JSONSchemaExtend(schema *jsonschema.Schema) {
deprecate(schema, "license_key", "license_secret_ref")
}
Expand Down Expand Up @@ -121,8 +130,8 @@ type Auth struct {
}

type TLS struct {
Enabled *bool `json:"enabled" jsonschema:"required"`
Certs *TLSCertMap `json:"certs"`
Enabled *bool `json:"enabled" jsonschema:"required"`
Certs TLSCertMap `json:"certs"`
}

type ExternalConfig struct {
Expand Down Expand Up @@ -487,7 +496,7 @@ type KafkaListeners struct {
AuthenticationMethod string `json:"authenticationMethod" jsonschema:"pattern=sasl|none|mtls_identity"`
External ExternalListeners[KafkaExternal] `json:"external"`
TLS *ExternalTLS `json:"tls" jsonschema:"required"`
Port int `json:"port" jsonschema:"required"`
Port int32 `json:"port" jsonschema:"required"`
}

type KafkaExternal struct {
Expand Down
27 changes: 26 additions & 1 deletion charts/redpanda/values_util.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
//+gotohelm:ignore=true
// +gotohelm:ignore=true
package redpanda

import (
"fmt"

"github.com/invopop/jsonschema"
corev1 "k8s.io/api/core/v1"
)

type TLSCertReference string

type ImageTag string

func (ImageTag) JSONSchemaExtend(schema *jsonschema.Schema) {
Expand Down Expand Up @@ -51,3 +54,25 @@ func deprecate(schema *jsonschema.Schema, keys ...string) {
prop.Deprecated = true
}
}

// FileSource ...
// +kubebuilder:validation:MaxProperties=1
type FileSource struct {
Path *string
Contents []byte
SecretKeyRef *corev1.SecretKeySelector
ConfigMapRef *corev1.ConfigMapKeySelector
}

// PerBrokerValue allows configuring a value per Redpanda Broker/Node/Pod.
type PerBrokerValue[T comparable] struct {
// Static, if provided, is a static value that will be set verbatim
// regardless of the broker.
Static *T
// ByOrdinal is a list of values that will be used
// It's length MUST be greater than or equal to (>=) the number of Redpanda
// Brokers.
ByOrdinal *[]T
// TODO decide how this will work.
Template *string
}

0 comments on commit b71d0fe

Please sign in to comment.