Implement TLS authentication for kafka mqt (#1300)

* use secrets to store keys and certificates
This commit is contained in:
Suraj Banakar
2019-10-10 00:18:57 +08:00
committed by Ta-Ching Chen
parent d358a29b17
commit 395a8adf37
5 changed files with 184 additions and 15 deletions
+65 -10
View File
@@ -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()
}
+3 -2
View File
@@ -42,8 +42,9 @@ type (
requestType int
MessageQueueConfig struct {
MQType string
Url string
MQType string
Url string
Secrets map[string][]byte
}
MessageQueue interface {
+54 -2
View File
@@ -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
}