Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#3] MQTTS support in the software-update's local connection #63

Merged
merged 3 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions internal/edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ package feature

import (
"encoding/json"
"net/url"

"github.com/eclipse-kanto/software-update/internal/logger"
"github.com/eclipse-kanto/software-update/util/tls"

MQTT "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
Expand Down Expand Up @@ -58,6 +60,17 @@ func newEdgeConnector(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig, ecl e
if len(scriptSUPConfig.Username) > 0 {
opts = opts.SetUsername(scriptSUPConfig.Username).SetPassword(scriptSUPConfig.Password)
}
u, err := url.Parse(scriptSUPConfig.Broker)
if err != nil {
return nil, err
}
if isConnectionSecure(u.Scheme) {
tlsConfig, err := tls.NewTLSConfig(scriptSUPConfig.CACert, scriptSUPConfig.Cert, scriptSUPConfig.Key)
if err != nil {
return nil, err
}
opts.SetTLSConfig(tlsConfig)
}

p := &EdgeConnector{mqttClient: MQTT.NewClient(opts), edgeClient: ecl}
if token := p.mqttClient.Connect(); token.Wait() && token.Error() != nil {
Expand Down Expand Up @@ -98,6 +111,15 @@ func newEdgeConnector(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig, ecl e
return p, nil
}

func isConnectionSecure(schema string) bool {
switch schema {
case "wss", "ssl", "tls", "mqtts", "mqtt+ssl", "tcps":
return true
default:
}
return false
}

// Close the EdgeConnector
func (p *EdgeConnector) Close() {
if p.cfg != nil {
Expand Down
9 changes: 9 additions & 0 deletions internal/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const (
defaultBroker = "tcp://localhost:1883"
defaultUsername = ""
defaultPassword = ""
defaultCACert = ""
defaultCert = ""
defaultKey = ""
defaultStorageLocation = "."
defaultFeatureID = "SoftwareUpdatable"
defaultModuleType = "software"
Expand Down Expand Up @@ -70,6 +73,9 @@ type ScriptBasedSoftwareUpdatableConfig struct {
Broker string `json:"broker,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
CACert string `json:"caCert,omitempty"`
Cert string `json:"cert,omitempty"`
Key string `json:"key,omitempty"`
StorageLocation string `json:"storageLocation,omitempty"`
FeatureID string `json:"featureId,omitempty"`
ModuleType string `json:"moduleType,omitempty"`
Expand Down Expand Up @@ -117,6 +123,9 @@ func NewDefaultConfig() *BasicConfig {
Broker: defaultBroker,
Username: defaultUsername,
Password: defaultPassword,
CACert: defaultCACert,
Cert: defaultCert,
Key: defaultKey,
StorageLocation: defaultStorageLocation,
FeatureID: defaultFeatureID,
ModuleType: defaultModuleType,
Expand Down
5 changes: 4 additions & 1 deletion internal/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ func InitFlags(flagSet *flag.FlagSet, cfg *BasicConfig) {
flagSet.StringVar(&cfg.Broker, "broker", cfg.Broker, "Local MQTT broker address")
flagSet.StringVar(&cfg.Username, "username", cfg.Username, "Username that is a part of the credentials")
flagSet.StringVar(&cfg.Password, "password", cfg.Password, "Password that is a part of the credentials")
flagSet.StringVar(&cfg.CACert, "caCert", cfg.CACert, "A PEM encoded CA certificates file for MQTT broker connection")
flagSet.StringVar(&cfg.Cert, "cert", cfg.Cert, "A PEM encoded certificate file to authenticate to the MQTT server/broker")
flagSet.StringVar(&cfg.Key, "key", cfg.Key, "A PEM encoded unencrypted private key file to authenticate to the MQTT server/broker")
flagSet.StringVar(&cfg.StorageLocation, "storageLocation", cfg.StorageLocation, "Location of the storage")
flagSet.StringVar(&cfg.FeatureID, "featureId", cfg.FeatureID, "Feature identifier of SoftwareUpdatable")
flagSet.StringVar(&cfg.ModuleType, "moduleType", cfg.ModuleType, "Module type of SoftwareUpdatable")
Expand All @@ -62,7 +65,7 @@ func InitFlags(flagSet *flag.FlagSet, cfg *BasicConfig) {
flagSet.StringVar(&cfg.Mode, "mode", cfg.Mode, modeDescription)

flagSet.Var(&cfg.InstallCommand, flagInstall, "Defines the absolute path to install script")
flagSet.Var(NewPathArgs(&cfg.InstallDirs), "installDirs", "Local file system directories, where to search for module artifacts")
flagSet.Var(newPathArgs(&cfg.InstallDirs), "installDirs", "Local file system directories, where to search for module artifacts")
flagSet.StringVar(&cfg.ConfigFile, flagConfigFile, cfg.ConfigFile, "Defines the configuration file")
}

Expand Down
12 changes: 12 additions & 0 deletions internal/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ func TestFlagsHasHigherPriority(t *testing.T) {
// 3. Test with all flags are applied instead of default values if no configuration JSON is provided
expectedFlagBroker := "host:1234"
expectedArtifact := "TestArchive"
expectedCACert := "TestCaCert.crt"
expectedCert := "TestCert.cert"
expectedKey := "TestKey.key"
expectedFeatureID := "TestFeature"
expectedInstall := "TestInstall"
expectedServerCert := "TestCert"
Expand All @@ -152,6 +155,9 @@ func TestFlagsHasHigherPriority(t *testing.T) {
setFlags([]string{
c(flagBroker, expectedFlagBroker),
c(flagArtifactType, expectedArtifact),
c(flagCACert, expectedCACert),
c(flagCert, expectedCert),
c(flagKey, expectedKey),
c(flagFeatureID, expectedFeatureID),
c(flagInstall, expectedInstall),
c(flagServerCert, expectedServerCert),
Expand Down Expand Up @@ -180,6 +186,9 @@ func TestFlagsHasHigherPriority(t *testing.T) {
Broker: expectedFlagBroker,
Username: expectedUsername,
Password: expectedPassword,
CACert: expectedCACert,
Cert: expectedCert,
Key: expectedKey,
ServerCert: expectedServerCert,
StorageLocation: expectedStorageLocation,
InstallCommand: command{cmd: expectedInstall},
Expand Down Expand Up @@ -353,6 +362,9 @@ func assertSoftwareUpdatable(t *testing.T, actual, expected ScriptBasedSoftwareU
assertString(t, actual.Broker, expected.Broker)
assertString(t, actual.Username, expected.Username)
assertString(t, actual.Password, expected.Password)
assertString(t, actual.CACert, expected.CACert)
assertString(t, actual.Cert, expected.Cert)
assertString(t, actual.Key, expected.Key)
assertString(t, actual.ServerCert, expected.ServerCert)
assertString(t, actual.StorageLocation, expected.StorageLocation)
assertInt(t, actual.DownloadRetryCount, expected.DownloadRetryCount)
Expand Down
4 changes: 2 additions & 2 deletions internal/path_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func (a *pathArgs) Set(value string) error {
return nil
}

// NewPathArgs creates new flag variable for slice of strings definition.
func NewPathArgs(setter *[]string) *pathArgs {
// newPathArgs creates new flag variable for slice of strings definition.
func newPathArgs(setter *[]string) *pathArgs {
return &pathArgs{
args: setter,
}
Expand Down
4 changes: 2 additions & 2 deletions internal/path_args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestPathArgsIsSet(t *testing.T) {
f := flag.NewFlagSet("testing", flag.ContinueOnError)

var s []string
v := NewPathArgs(&s)
v := newPathArgs(&s)
f.Var(v, "S", "S")

args := []string{"-S=a b"}
Expand All @@ -47,7 +47,7 @@ func TestPathArgsInvalid(t *testing.T) {
f.SetOutput(io.Discard)

var s []string
v := NewPathArgs(&s)
v := newPathArgs(&s)
f.Var(v, "S", "S")

args := []string{"-S="}
Expand Down
3 changes: 3 additions & 0 deletions internal/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const (
flagBroker = "broker"
flagUsername = "username"
flagPassword = "password"
flagCACert = "caCert"
flagCert = "cert"
flagKey = "key"
flagStorageLocation = "storageLocation"
flagFeatureID = "featureId"
flagModuleType = "moduleType"
Expand Down
22 changes: 22 additions & 0 deletions util/tls/testdata/ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDkzCCAnugAwIBAgIUbD0mjn2x1H5VKi54xgjfPsEugzAwDQYJKoZIhvcNAQEL
BQAwWTELMAkGA1UEBhMCQkcxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X
DTIyMTAyNzE1MDk0OFoXDTI3MTAyNzE1MDk0OFowWTELMAkGA1UEBhMCQkcxEzAR
BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5
IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAqRZyG9gywFvplpfPq60X7JYtkbj7OF+tOfixF9FkGDJ9Hhh4LoMv
W+rdLnxEHfI4Vicn0Wj3r/Ra1bd9yo8hzGiYdNliH9kegSbjB2Xx8N4yTCqaiZ9E
JQLcstQeXHEP3YwPXLnNfTOmbQPAbC2T9J+USlmolG1qpkuU5rQVC/sjW5M8MOmN
TuKEU6pds/j8GQKhQmsIHddwfypnBDilpYOotgeMwDqsyM4+zdSXbFaNmYoh3Tjb
FSN0WJTdPe7uv+nG03NZe6dHvN4C/8Z5uBIkeLw8yrO/2Wb7aAxwtpK3Wswzlya9
TAwALiOI+hWuIfUmkoQapskIMhcRAnpMxwIDAQABo1MwUTAdBgNVHQ4EFgQUH2fo
QuQyEoszAL3vBOBqs8OLg7YwHwYDVR0jBBgwFoAUH2foQuQyEoszAL3vBOBqs8OL
g7YwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYNY+8FNt9Pr/
I6NYzY56opMIXmMErRMBN4i5WkuZxXp7gaqPNrx42O7gaWJz7dwwGUmrb0eHyOIm
XetphU6cW1vNgaXPOggacp2wRJ6AGJn1+v/hfWU6sPt0XWPM9p5umoCeYJeZ1UlE
uodsUGEQc7b/ODROObPHAFc/18nChoiPylXtB5TcgdyzalzhL/d8B/c4QZJwvT+W
L+8IoChNQzeH4yCgoDZXaQpRfrnGjLyrpx4dojNBYd/rJsGNZa+wMwzhFZU3f3QY
jeZnnp+nVBw+/L3q/FVwCee/RsYiR797OL7wyPAJPGd4iqbh09Hv0B2YgIih86X6
giPwyRzg7g==
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions util/tls/testdata/certificate.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEjzCCAncCFAEWn/QU1vzg07IeNzhaX/6pNlTeMA0GCSqGSIb3DQEBCwUAMIGD
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu
IEZyYW5jaXNjbzEVMBMGA1UECgwMRXhhbXBsZSBJbmMuMRYwFAYDVQQLDA1JVCBE
ZXBhcnRtZW50MRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wHhcNMjEwNTEzMDg0
NTU3WhcNMjExMDEwMDg0NTU3WjCBgzELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDU1h
c3NhY2h1c2V0dHMxDzANBgNVBAcMBkJvc3RvbjEVMBMGA1UECgwMRXhhbXBsZSBJ
bmMuMRYwFAYDVQQLDA1IUiBEZXBhcnRtZW50MRwwGgYDVQQDDBN3d3cuZXhhbXBs
ZS1pbmMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApmF7J4WB
BujYYNt86YHSWrv3ffX3Odt57y4kqhWuMM8VcA2RXTlJO3SXX8xLF/+lsaZZJmfg
xR1tJ0hKfBYt78H03bjrylWLOQoRqlyVuz0SF3ueR9vx3mPIO0F0E3Q2mC4SLHbr
5kW4aj3/NszLzgvZPbOchfcktdCd1vME+pM8lPPY6z8qWJzlWjOYdymWUV8z9qlA
c3VCnYQ+UwJY51vTcMPpalByrMPWkiNic+9Onl8KHCik27vNMIVVLYLa6763UwU8
qCKT+jZj6nlCT8w5oqoJNsaSd/EGGFtGR+qZj6V2TrsI2cNknyY7Qf/QN0+zH0nm
XsOxK6jW8q7RGwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBe/kcT2L54PxZqb3GU
liwYJGjB+9fkqTyMwglt8dAm3it9F/POyXtoKB8a1AuaZ/FJlR+AUOFv+f3i0ZnE
Ek0OAsllVPclv7HhywD1HzrbLh0PreGsBnYgyrW7qZKAfevus0U0GrjhcrY7zCoA
EBFWWqcWqhRCFXYwgI13ZNLhYl7r+NIWLza1bPcnWVfY2g19/nctR53ZFFiVkvlk
FCYGat0SPQWvjFIKaCNQQL6IZSxqk95W87kEWrac9A+bQzENpWLfwu86O5r6vKNK
pHmd47Sy8hyhf75/SEOQuWBEkgT+sPXU7TykvFB8kzO1Wmsz7D2/d5pvkjZF31dQ
m4ZHIuclPOETtAwiY13dI94vAhgruK0FRFn7jyfePn20CFqUCOO9cEQytysCO/n7
4xJPbIVcvUO825Kbos71OWfNkLEi1tlLkFpe73/rSXnZRWweqAThrY7jxGxhveI4
iYrOOYEqGdM6VfLvVhYXhsc/MDqiqJLdbhQAS/lE8bJrnVnVRYnnb0ExfNTwqVU3
8YZB6JbT+j9556c675j0sfa0J8qgDRHsWR7EG5u5wENnDH/s142dYcjJ5S6tCumM
K/GExzmrJFHmLfqKTSxAquMYvDVufICcklL067DacRJKHkyx5KvKkgc3R8rTaX2o
D+0ioElFJXVQ7rULiWzxZs+Gdw==
-----END CERTIFICATE-----
Empty file added util/tls/testdata/empty.crt
Empty file.
1 change: 1 addition & 0 deletions util/tls/testdata/invalid.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
---invalid data---
27 changes: 27 additions & 0 deletions util/tls/testdata/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApmF7J4WBBujYYNt86YHSWrv3ffX3Odt57y4kqhWuMM8VcA2R
XTlJO3SXX8xLF/+lsaZZJmfgxR1tJ0hKfBYt78H03bjrylWLOQoRqlyVuz0SF3ue
R9vx3mPIO0F0E3Q2mC4SLHbr5kW4aj3/NszLzgvZPbOchfcktdCd1vME+pM8lPPY
6z8qWJzlWjOYdymWUV8z9qlAc3VCnYQ+UwJY51vTcMPpalByrMPWkiNic+9Onl8K
HCik27vNMIVVLYLa6763UwU8qCKT+jZj6nlCT8w5oqoJNsaSd/EGGFtGR+qZj6V2
TrsI2cNknyY7Qf/QN0+zH0nmXsOxK6jW8q7RGwIDAQABAoIBAH09m6qgQAOnellO
XrSW2HUcUKwsXjDbGOoF3et57mknOIfkbqux14I9vUSLT2t9MIiNI0ZZo0Q9ZlDP
heHqACId6eiMrlDcG7SP88Q9dShATEII95g349T3X13bYzjRndbntx5pViE8EhlH
GblyZ2duW9SqQwREiQmjQ2zt+a1zuKUAGdysS+4101UHUj6tC/RjiNN/TCXXRJIX
GOJ4WFLY+f2bXgSmqbK7wqN9nPxmxl/+bv4hO32Gsv06ejuy/6+GFJZa+n3ASU2r
/ptr4vgCK+t6I0OWVTpvYUboEwAXam4JfAu12zLtczfXVFJjzklUvSClIG9aJ6DP
2B3LEuECgYEA1RenCuVl6sM2o858X8iOLTMuCZWX5VFKY1+vvLwB/+CBfy7/dDYw
lbv+xaots0rY9Wn784ewi9zJbdnXE1YNgj0utIMHzylXvTolnDYsoO7SYpIwqtpa
PyzPcAV3Khkd5LGe1hf9VmOJfTF/563ztLXip0HUeIvzgB/maqfAQW8CgYEAx+H4
GZ3ycdL03x7Zvp5g5yDPZGvxqVIEIFmliagEFyBgqogbXuOvsvZDTg+gKV5/QyVg
FWokz5VtC6U9UcWfF7LJses/Hsedh9IeICd9UIzkey8UmS2mDBeGfTHVTNuczml5
VzmTK8jGUO5aRVRyOt0tqVf6Oozo8ImdI1fOPRUCgYBI4p4wC+agNcUqoiXIXUDE
FQ1aGeCqfvOCqefiFixY6OFiLyERDrfvfy3VTi/zc1ZiGq4izfaE4C/Fcw0tf/F+
6o5fD7JMGUf5YTocBCufoBA1xur+hVD46srI9hWcQJsI7ff2Ip50Pfd46sVk6QrC
dLPhoZKa6MOQv1iAgoAv4QKBgQDAfiO6N9vmNizQOxujcU8NBxHzOekvEOccaHj9
Cqt1wh6V3CHPziHEjVjf8jhh3rlcZsATn3b32oV7c5SMDW9bGTkYeN7+u2pABOAy
QxVx312iLAMASW/hsT45jyZFsDFgrz7F+5J51g72nbSdk+e2PI7eyPUYMd+a1kxY
XxUkyQKBgBde+jg2UjGHAfW6ZWSPHWvi74oD42VzbJg6/o/KFaZGEyHdKWgrwBW7
8wNyNTMOMSHyuvtMnc5oLd492aO2a+yurgzt7yIelVuphSKda0wd/0PljKb0lwlb
VzldR4bXolbyjNXky1KWmvSNkmbPFNGSCeyHmKQgSLXKGuWbj4ic
-----END RSA PRIVATE KEY-----
60 changes: 60 additions & 0 deletions util/tls/tls_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2023 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0

package tls

import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
)

// NewTLSConfig initializes the TLS.
func NewTLSConfig(rootCert, cert, key string) (*tls.Config, error) {
fmt.Println("NewTLS CONFIG!", rootCert)
caCert, err := os.ReadFile(rootCert)
if err != nil {
return nil, fmt.Errorf("failed to load CA: %s", err)
}
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(caCert) {
return nil, fmt.Errorf("failed to parse CA %s", rootCert)
}

tlsConfig := &tls.Config{
InsecureSkipVerify: false,
RootCAs: caCertPool,
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
CipherSuites: supportedCipherSuites(),
}

if len(cert) > 0 || len(key) > 0 {
cert, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return nil, fmt.Errorf("failed to load X509 key pair: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}

return tlsConfig, nil
}

func supportedCipherSuites() []uint16 {
cs := tls.CipherSuites()
cid := make([]uint16, len(cs))
for i := range cs {
cid[i] = cs[i].ID
}
return cid
}
Loading