Skip to content

Commit

Permalink
fixup! bridge: use secured TLS communication
Browse files Browse the repository at this point in the history
  • Loading branch information
Danielius1922 committed Jan 31, 2024
1 parent c9ba38a commit 2364ad8
Show file tree
Hide file tree
Showing 33 changed files with 568 additions and 199 deletions.
39 changes: 33 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,50 @@ test: env build-testcontainer
$(SERVICE_NAME):$(VERSION_TAG) -test.parallel 1 -test.v -test.coverprofile=/tmp/coverage.txt

test-bridge:
sudo rm -rf $(TMP_PATH)/data || :
mkdir -p $(TMP_PATH)/data
# pull image
docker pull $(HUB_TEST_DEVICE_IMAGE)
# prepare environment
docker run \
--rm \
--network=host \
--name hub-device-tests-environment \
--env PREPARE_ENV=true \
--env RUN=false \
--env COAP_GATEWAY_CLOUD_ID="$(CLOUD_SID)" \
-v $(TMP_PATH):/tmp \
-v $(TMP_PATH)/data:/data \
$(HUB_TEST_DEVICE_IMAGE)

# start device
rm -rf $(TMP_PATH)/bridge || :
mkdir -p $(TMP_PATH)/bridge
go build -C ./test/ocfbridge -cover -o ./ocfbridge
pkill -KILL ocfbridge || :
GOCOVERDIR=$(TMP_PATH)/bridge ./test/ocfbridge/ocfbridge -config ./test/ocfbridge/config.yaml &
CLOUD_SID=$(CLOUD_SID) CA_POOL=$(TMP_PATH)/data/certs/root_ca.crt \
CERT_FILE=$(TMP_PATH)/data/certs/external/coap-gateway.crt \
KEY_FILE=$(TMP_PATH)/data/certs/external/coap-gateway.key \
GOCOVERDIR=$(TMP_PATH)/bridge \
./test/ocfbridge/ocfbridge &

docker pull $(HUB_TEST_DEVICE_IMAGE) && \
# run tests
docker run \
--network=host \
--rm \
--network=host \
--name hub-device-tests \
--env PREPARE_ENV=false \
--env RUN=true \
--env COAP_GATEWAY_CLOUD_ID="$(CLOUD_SID)" \
--env TEST_DEVICE_NAME="bridged-device-0" \
--env TEST_DEVICE_TYPE="bridged" \
--env GRPC_GATEWAY_TEST_DISABLED=1 \
--env IOTIVITY_LITE_TEST_RUN="(TestOffboard|TestOffboardWithoutSignIn|TestOffboardWithRepeat|TestRepublishAfterRefresh)$$" \
-v $(TMP_PATH):/tmp \
-v $(TMP_PATH)/data:/data \
$(HUB_TEST_DEVICE_IMAGE)

# stop device
pkill -TERM ocfbridge || :
while pgrep -x ocfbridge > /dev/null; do \
echo "waiting for ocfbridge to exit"; \
Expand All @@ -150,9 +176,10 @@ test-bridge:
go tool covdata textfmt -i=$(TMP_PATH)/bridge -o $(TMP_PATH)/bridge.coverage.txt

clean:
docker rm -f devsim-net-host || true
docker rm -f hub-device-tests || true
pkill -KILL ocfbridge || true
docker rm -f devsim-net-host || :
docker rm -f hub-device-tests-environment || :
docker rm -f hub-device-tests || :
pkill -KILL ocfbridge || :
sudo rm -rf .tmp/*

.PHONY: build-testcontainer build certificates clean env test unit-test
71 changes: 18 additions & 53 deletions bridge/device/cloud/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import (

type (
GetLinksFilteredBy func(endpoints schema.Endpoints, deviceIDfilter uuid.UUID, resourceTypesFitler []string, policyBitMaskFitler schema.BitMask) (links schema.ResourceLinks)
GetCAPool func() []*x509.Certificate
GetCertificates func(deviceID string) []tls.Certificate
)

Expand All @@ -69,7 +68,7 @@ type Manager struct {
maxMessageSize uint32
deviceID uuid.UUID
save func()
getCAPool func() []*x509.Certificate
caPool CAPool
getCertificates func(deviceID string) []tls.Certificate

private struct {
Expand All @@ -85,38 +84,12 @@ type Manager struct {
trigger chan bool
}

type OptionsCfg struct {
maxMessageSize uint32
getCAPool GetCAPool
getCertificates GetCertificates
}

type Option func(*OptionsCfg)

func WithMaxMessageSize(maxMessageSize uint32) Option {
return func(o *OptionsCfg) {
if maxMessageSize > 0 {
o.maxMessageSize = maxMessageSize
}
}
}

func WithGetCAPool(getCAPool GetCAPool) Option {
return func(o *OptionsCfg) {
o.getCAPool = getCAPool
}
}

func WithGetCertificates(getCertificates GetCertificates) Option {
return func(o *OptionsCfg) {
o.getCertificates = getCertificates
func New(deviceID uuid.UUID, save func(), handler net.RequestHandler, getLinks GetLinksFilteredBy, caPool CAPool, opts ...Option) (*Manager, error) {
if !caPool.IsValid() {
return nil, fmt.Errorf("invalid ca pool")
}
}

func New(deviceID uuid.UUID, save func(), handler net.RequestHandler, getLinks GetLinksFilteredBy, opts ...Option) *Manager {
o := OptionsCfg{
maxMessageSize: net.DefaultMaxMessageSize,
getCAPool: func() []*x509.Certificate { return nil },
getCertificates: func(string) []tls.Certificate {
return nil
},
Expand All @@ -133,11 +106,11 @@ func New(deviceID uuid.UUID, save func(), handler net.RequestHandler, getLinks G
deviceID: deviceID,
maxMessageSize: o.maxMessageSize,
save: save,
getCAPool: o.getCAPool,
caPool: caPool,
getCertificates: o.getCertificates,
}
c.private.cfg.ProvisioningStatus = cloud.ProvisioningStatus_UNINITIALIZED
return c
return c, nil
}

func (c *Manager) Get(request *net.Request) (*pool.Message, error) {
Expand Down Expand Up @@ -351,28 +324,20 @@ func (c *Manager) dial(ctx context.Context) error {
_ = c.close()
cfg := c.getCloudConfiguration()

var verifyPeer func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
var rootCAs *x509.CertPool
cas := c.getCAPool()
if (len(cas)) > 0 {
rootCAs = x509.NewCertPool()
for _, ca := range cas {
rootCAs.AddCert(ca)
}
verifyPeer = coap.NewVerifyPeerCertificate(rootCAs, func(cert *x509.Certificate) error {
cloudID, err := uuid.Parse(c.getCloudConfiguration().CloudID)
if err != nil {
return fmt.Errorf("cannot parse cloudID: %w", err)
}
return coap.VerifyCloudCertificate(cert, cloudID)
})
caPool, err := c.caPool.GetPool()
if err != nil {
return fmt.Errorf("cannot get ca pool: %w", err)
}

tlsConfig := &tls.Config{
InsecureSkipVerify: true, //nolint:gosec
RootCAs: rootCAs,
Certificates: c.getCertificates(c.deviceID.String()),
VerifyPeerCertificate: verifyPeer,
InsecureSkipVerify: true, //nolint:gosec
Certificates: c.getCertificates(c.deviceID.String()),
VerifyPeerCertificate: coap.NewVerifyPeerCertificate(caPool, func(cert *x509.Certificate) error {
cloudID, errP := uuid.Parse(c.getCloudConfiguration().CloudID)
if errP != nil {
return fmt.Errorf("cannot parse cloudID: %w", errP)
}
return coap.VerifyCloudCertificate(cert, cloudID)
}),
}

ep := schema.Endpoint{
Expand Down
40 changes: 40 additions & 0 deletions bridge/device/cloud/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/****************************************************************************
*
* Copyright (c) 2023 plgd.dev s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License"),
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************/

package cloud

type OptionsCfg struct {
maxMessageSize uint32
getCertificates GetCertificates
}

type Option func(*OptionsCfg)

func WithMaxMessageSize(maxMessageSize uint32) Option {
return func(o *OptionsCfg) {
if maxMessageSize > 0 {
o.maxMessageSize = maxMessageSize
}
}
}

func WithGetCertificates(getCertificates GetCertificates) Option {
return func(o *OptionsCfg) {
o.getCertificates = getCertificates
}
}
59 changes: 59 additions & 0 deletions bridge/device/cloud/security.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/****************************************************************************
*
* Copyright (c) 2023 plgd.dev s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License"),
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************/

package cloud

import (
"crypto/x509"
"fmt"
)

type GetCAPool func() []*x509.Certificate

type CAPool struct {
getCAPool GetCAPool
useSystemCAPool bool
}

func MakeCAPool(getCAPool GetCAPool, useSystemCAPool bool) CAPool {
return CAPool{
getCAPool: getCAPool,
useSystemCAPool: useSystemCAPool,
}
}

func (c *CAPool) IsValid() bool {
return c.useSystemCAPool || (c.getCAPool != nil && c.getCAPool() != nil)
}

func (c *CAPool) GetPool() (*x509.CertPool, error) {
var pool *x509.CertPool
if c.useSystemCAPool {
systemPool, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("cannot get system pool: %w", err)
}
pool = systemPool
} else {
pool = x509.NewCertPool()
}
for _, ca := range c.getCAPool() {
pool.AddCert(ca)
}
return pool, nil
}
46 changes: 25 additions & 21 deletions bridge/device/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ type OnDeviceUpdated func(d *Device)
type OptionsCfg struct {
onDeviceUpdated OnDeviceUpdated
getAdditionalProperties resourcesDevice.GetAdditionalPropertiesForResponseFunc
getCAPool cloud.GetCAPool
getCertificates cloud.GetCertificates
caPool cloud.CAPool
}

type Option func(*OptionsCfg)
Expand All @@ -108,24 +108,25 @@ func WithGetAdditionalPropertiesForResponse(getAdditionalProperties resourcesDev
}
}

func WithGetCAPool(getCAPool cloud.GetCAPool) Option {
func WithGetCertificates(getCertificates cloud.GetCertificates) Option {
return func(o *OptionsCfg) {
o.getCAPool = getCAPool
o.getCertificates = getCertificates
}
}

func WithGetCertificates(getCertificates cloud.GetCertificates) Option {
func WithCAPool(caPool cloud.CAPool) Option {
return func(o *OptionsCfg) {
o.getCertificates = getCertificates
o.caPool = caPool
}
}

func New(cfg Config, opts ...Option) *Device {
func New(cfg Config, opts ...Option) (*Device, error) {
o := OptionsCfg{
onDeviceUpdated: func(d *Device) {
// do nothing
},
getAdditionalProperties: func() map[string]interface{} { return nil },
caPool: cloud.MakeCAPool(nil, false),
}
for _, opt := range opts {
opt(&o)
Expand All @@ -137,31 +138,34 @@ func New(cfg Config, opts ...Option) *Device {
resources: sync.NewMap[string, Resource](),
onDeviceUpdated: o.onDeviceUpdated,
}
d.AddResource(resourcesDevice.New(plgdDevice.ResourceURI, d, o.getAdditionalProperties))
// oic/res is not discoverable
discoverResource := discovery.New(plgdResources.ResourceURI, d.GetLinks)
discoverResource.PolicyBitMask = schema.Discoverable
d.AddResource(discoverResource)

d.AddResource(maintenance.New(maintenanceSchema.ResourceURI, func() {
d.UnregisterFromCloud()
}))

if cfg.Cloud.Enabled {
opts := []cloud.Option{cloud.WithMaxMessageSize(cfg.MaxMessageSize)}
if o.getCAPool != nil {
opts = append(opts, cloud.WithGetCAPool(o.getCAPool))
}
if o.getCertificates != nil {
opts = append(opts, cloud.WithGetCertificates(o.getCertificates))
}
d.cloudManager = cloud.New(d.cfg.ID, func() {
cm, err := cloud.New(d.cfg.ID, func() {
d.onDeviceUpdated(d)
}, d.HandleRequest, d.GetLinksFilteredBy, opts...)
}, d.HandleRequest, d.GetLinksFilteredBy, o.caPool, opts...)
if err != nil {
return nil, fmt.Errorf("cannot create cloud manager: %w", err)
}
d.cloudManager = cm
d.AddResource(cloudResource.New(cloudSchema.ResourceURI, d.cloudManager))
d.cloudManager.ImportConfig(cfg.Cloud.Config)
}
return d

d.AddResource(resourcesDevice.New(plgdDevice.ResourceURI, d, o.getAdditionalProperties))
// oic/res is not discoverable
discoverResource := discovery.New(plgdResources.ResourceURI, d.GetLinks)
discoverResource.PolicyBitMask = schema.Discoverable
d.AddResource(discoverResource)

d.AddResource(maintenance.New(maintenanceSchema.ResourceURI, func() {
d.UnregisterFromCloud()
}))

return d, nil
}

func (d *Device) AddResource(resource Resource) {
Expand Down
Loading

0 comments on commit 2364ad8

Please sign in to comment.