Implement TLS authentication for kafka mqt (#1300)
* use secrets to store keys and certificates
This commit is contained in:
committed by
Ta-Ching Chen
parent
d358a29b17
commit
395a8adf37
@@ -17,10 +17,13 @@ limitations under the License.
|
||||
package messageQueue
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
sarama "github.com/Shopify/sarama"
|
||||
@@ -39,6 +42,8 @@ type (
|
||||
routerUrl string
|
||||
brokers []string
|
||||
version sarama.KafkaVersion
|
||||
authKeys map[string][]byte
|
||||
tls bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -64,9 +69,23 @@ func makeKafkaMessageQueue(logger *zap.Logger, routerUrl string, mqCfg MessageQu
|
||||
version: kafkaVersion,
|
||||
}
|
||||
|
||||
if tls, _ := strconv.ParseBool(os.Getenv("TLS_ENABLED")); tls == true {
|
||||
kafka.tls = true
|
||||
|
||||
authKeys := make(map[string][]byte)
|
||||
|
||||
if mqCfg.Secrets == nil {
|
||||
return nil, errors.New("no secrets were loaded")
|
||||
}
|
||||
|
||||
authKeys["caCert"] = mqCfg.Secrets["caCert"]
|
||||
authKeys["userCert"] = mqCfg.Secrets["userCert"]
|
||||
authKeys["userKey"] = mqCfg.Secrets["userKey"]
|
||||
kafka.authKeys = authKeys
|
||||
}
|
||||
|
||||
logger.Info("created kafka queue", zap.Any("kafka brokers", kafka.brokers),
|
||||
zap.Any("kafka version", kafka.version))
|
||||
|
||||
return kafka, nil
|
||||
}
|
||||
|
||||
@@ -83,6 +102,28 @@ func (kafka Kafka) subscribe(trigger *fv1.MessageQueueTrigger) (messageQueueSubs
|
||||
consumerConfig.Consumer.Return.Errors = true
|
||||
consumerConfig.Group.Return.Notifications = true
|
||||
consumerConfig.Config.Version = kafka.version
|
||||
|
||||
// Create new producer
|
||||
producerConfig := sarama.NewConfig()
|
||||
producerConfig.Producer.RequiredAcks = sarama.WaitForAll
|
||||
producerConfig.Producer.Retry.Max = 10
|
||||
producerConfig.Producer.Return.Successes = true
|
||||
producerConfig.Version = kafka.version
|
||||
|
||||
// Setup TLS for both producer and consumer
|
||||
if kafka.tls {
|
||||
consumerConfig.Net.TLS.Enable = true
|
||||
producerConfig.Net.TLS.Enable = true
|
||||
tlsConfig, err := kafka.getTLSConfig()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
producerConfig.Net.TLS.Config = tlsConfig
|
||||
consumerConfig.Net.TLS.Config = tlsConfig
|
||||
}
|
||||
|
||||
consumer, err := cluster.NewConsumer(kafka.brokers, string(trigger.Metadata.UID), []string{trigger.Spec.Topic}, consumerConfig)
|
||||
kafka.logger.Info("created a new consumer", zap.Strings("brokers", kafka.brokers),
|
||||
zap.String("input topic", trigger.Spec.Topic),
|
||||
@@ -91,17 +132,10 @@ func (kafka Kafka) subscribe(trigger *fv1.MessageQueueTrigger) (messageQueueSubs
|
||||
zap.String("trigger name", trigger.Metadata.Name),
|
||||
zap.String("function namespace", trigger.Metadata.Namespace),
|
||||
zap.String("function name", trigger.Spec.FunctionReference.Name))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create new producer
|
||||
producerConfig := sarama.NewConfig()
|
||||
producerConfig.Producer.RequiredAcks = sarama.WaitForAll
|
||||
producerConfig.Producer.Retry.Max = 10
|
||||
producerConfig.Producer.Return.Successes = true
|
||||
producerConfig.Version = kafka.version
|
||||
producer, err := sarama.NewSyncProducer(kafka.brokers, producerConfig)
|
||||
kafka.logger.Info("created a new producer", zap.Strings("brokers", kafka.brokers),
|
||||
zap.String("input topic", trigger.Spec.Topic),
|
||||
@@ -112,7 +146,7 @@ func (kafka Kafka) subscribe(trigger *fv1.MessageQueueTrigger) (messageQueueSubs
|
||||
zap.String("function name", trigger.Spec.FunctionReference.Name))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// consume errors
|
||||
@@ -142,6 +176,27 @@ func (kafka Kafka) subscribe(trigger *fv1.MessageQueueTrigger) (messageQueueSubs
|
||||
return consumer, nil
|
||||
}
|
||||
|
||||
func (kafka Kafka) getTLSConfig() (*tls.Config, error) {
|
||||
tlsConfig := tls.Config{}
|
||||
cert, err := tls.X509KeyPair(kafka.authKeys["userCert"], kafka.authKeys["userKey"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(kafka.authKeys["caCert"])
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
|
||||
return &tlsConfig, nil
|
||||
}
|
||||
|
||||
func (kafka Kafka) unsubscribe(subscription messageQueueSubscription) error {
|
||||
return subscription.(*cluster.Consumer).Close()
|
||||
}
|
||||
|
||||
@@ -42,8 +42,9 @@ type (
|
||||
requestType int
|
||||
|
||||
MessageQueueConfig struct {
|
||||
MQType string
|
||||
Url string
|
||||
MQType string
|
||||
Url string
|
||||
Secrets map[string][]byte
|
||||
}
|
||||
|
||||
MessageQueue interface {
|
||||
|
||||
@@ -17,7 +17,11 @@ limitations under the License.
|
||||
package mqtrigger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
@@ -28,6 +32,7 @@ import (
|
||||
|
||||
func Start(logger *zap.Logger, routerUrl string) error {
|
||||
fissionClient, _, _, err := crd.MakeFissionClient()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get fission or kubernetes client")
|
||||
}
|
||||
@@ -40,10 +45,57 @@ func Start(logger *zap.Logger, routerUrl string) error {
|
||||
// Message queue type: nats is the only supported one for now
|
||||
mqType := os.Getenv("MESSAGE_QUEUE_TYPE")
|
||||
mqUrl := os.Getenv("MESSAGE_QUEUE_URL")
|
||||
|
||||
secretsPath := strings.TrimSpace(os.Getenv("MESSAGE_QUEUE_SECRETS"))
|
||||
|
||||
var secrets map[string][]byte
|
||||
if len(secretsPath) > 0 {
|
||||
// For authentication with message queue
|
||||
secrets, err = readSecrets(logger, secretsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mqCfg := messageQueue.MessageQueueConfig{
|
||||
MQType: mqType,
|
||||
Url: mqUrl,
|
||||
MQType: mqType,
|
||||
Url: mqUrl,
|
||||
Secrets: secrets,
|
||||
}
|
||||
messageQueue.MakeMessageQueueTriggerManager(logger, fissionClient, routerUrl, mqCfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func readSecrets(logger *zap.Logger, secretsPath string) (map[string][]byte, error) {
|
||||
|
||||
// return if no secrets exist
|
||||
if _, err := os.Stat(secretsPath); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secretFiles, err := ioutil.ReadDir(secretsPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secrets := make(map[string][]byte)
|
||||
for _, secretFile := range secretFiles {
|
||||
|
||||
fileName := secretFile.Name()
|
||||
// /etc/secrets contain some hidden directories (like .data)
|
||||
// ignore them
|
||||
if !secretFile.IsDir() && !strings.HasPrefix(fileName, ".") {
|
||||
logger.Info(fmt.Sprintf("Reading secret from %s", fileName))
|
||||
|
||||
filePath := path.Join(secretsPath, fileName)
|
||||
secret, fileReadErr := ioutil.ReadFile(filePath)
|
||||
if fileReadErr != nil {
|
||||
return nil, fileReadErr
|
||||
}
|
||||
|
||||
secrets[fileName] = secret
|
||||
}
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user