Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
66 changes: 64 additions & 2 deletions cmd/coap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ import (
"context"
"fmt"
"log"
"log/slog"
"net/url"
"os"

chclient "github.com/absmach/callhome/pkg/client"
"github.com/absmach/mgate"
mgatecoap "github.com/absmach/mgate/pkg/coap"
"github.com/absmach/mgate/pkg/session"
mgtls "github.com/absmach/mgate/pkg/tls"
"github.com/absmach/supermq"
"github.com/absmach/supermq/coap"
httpapi "github.com/absmach/supermq/coap/api"
Expand All @@ -30,19 +35,23 @@ import (
httpserver "github.com/absmach/supermq/pkg/server/http"
"github.com/absmach/supermq/pkg/uuid"
"github.com/caarlos0/env/v11"
"github.com/pion/dtls/v3"
"golang.org/x/sync/errgroup"
)

const (
svcName = "coap_adapter"
envPrefix = "SMQ_COAP_ADAPTER_"
envPrefixHTTP = "SMQ_COAP_ADAPTER_HTTP_"
envPrefixDTLS = "SMQ_COAP_ADAPTER_SERVER_"
envPrefixCache = "SMQ_COAP_CACHE_"
envPrefixClients = "SMQ_CLIENTS_GRPC_"
envPrefixChannels = "SMQ_CHANNELS_GRPC_"
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
defSvcHTTPPort = "5683"
defSvcCoAPPort = "5683"
targetProtocol = "coap"
targetCoapPort = "5682"
)

type config struct {
Expand Down Expand Up @@ -94,6 +103,13 @@ func main() {
return
}

dtlsCfg, err := mgtls.NewConfig(env.Options{Prefix: envPrefixDTLS})
if err != nil {
logger.Error(fmt.Sprintf("failed to load %s DTLS configuration : %s", svcName, err))
exitCode = 1
return
}

cacheConfig := messaging.CacheConfig{}
if err := env.ParseWithOptions(&cacheConfig, env.Options{Prefix: envPrefixCache}); err != nil {
logger.Error(fmt.Sprintf("failed to load cache configuration : %s", err))
Expand Down Expand Up @@ -196,7 +212,7 @@ func main() {
exitCode = 1
return
}
cs := coapserver.NewServer(ctx, cancel, svcName, coapServerConfig, httpapi.MakeCoAPHandler(svc, channelsClient, parser, logger), logger)
cs := coapserver.NewServer(ctx, cancel, svcName, server.Config{Host: coapServerConfig.Host, Port: targetCoapPort}, httpapi.MakeCoAPHandler(svc, channelsClient, parser, logger), logger)

if cfg.SendTelemetry {
chc := chclient.New(svcName, supermq.Version, logger, cancel)
Expand All @@ -207,7 +223,11 @@ func main() {
return hs.Start()
})
g.Go(func() error {
return cs.Start()
g.Go(func() error {
return cs.Start()
})
handler := coap.NewHandler(logger, clientsClient, channelsClient, parser)
return proxyCoAP(ctx, coapServerConfig, dtlsCfg, handler, logger)
})
g.Go(func() error {
return server.StopSignalHandler(ctx, cancel, logger, svcName, hs, cs)
Expand All @@ -217,3 +237,45 @@ func main() {
logger.Error(fmt.Sprintf("CoAP adapter service terminated: %s", err))
}
}

func proxyCoAP(ctx context.Context, cfg server.Config, dtlsCfg mgtls.Config, handler session.Handler, logger *slog.Logger) error {
var err error
config := mgate.Config{
Host: "",
Port: cfg.Port,
TargetProtocol: targetProtocol,
TargetHost: cfg.Host,
TargetPort: targetCoapPort,
}

mg := mgatecoap.NewProxy(config, handler, logger)

errCh := make(chan error)

config.DTLSConfig, err = mgtls.LoadTLSConfig(&dtlsCfg, &dtls.Config{})
if err != nil {
return err
}

switch {
case config.DTLSConfig != nil:
dltsCfg := config
mgDtls := mgatecoap.NewProxy(dltsCfg, handler, logger)
logger.Info(fmt.Sprintf("Starting COAP with DTLS proxy on port %s", cfg.Port))
go func() {
errCh <- mgDtls.Listen(ctx)
}()
default:
logger.Info(fmt.Sprintf("Starting COAP without DTLS proxy on port %s", cfg.Port))
go func() {
errCh <- mg.Listen(ctx)
}()
}
select {
case <-ctx.Done():
logger.Info(fmt.Sprintf("proxy COAP shutdown at %s:%s", config.Host, config.Port))
return nil
case err := <-errCh:
return err
}
}
43 changes: 0 additions & 43 deletions coap/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,6 @@ func (svc *adapterService) Publish(ctx context.Context, key string, msg *messagi
if topicType == messaging.HealthType {
return nil
}

authzRes, err := svc.channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{
DomainId: msg.GetDomain(),
ClientId: authnRes.GetId(),
ClientType: policies.ClientType,
Type: uint32(connections.Publish),
ChannelId: msg.GetChannel(),
})
if err != nil {
return errors.Wrap(svcerr.ErrAuthorization, err)
}
if !authzRes.Authorized {
return svcerr.ErrAuthorization
}

msg.Publisher = authnRes.GetId()

return svc.pubsub.Publish(ctx, messaging.EncodeMessageTopic(msg), msg)
Expand All @@ -105,19 +90,6 @@ func (svc *adapterService) Subscribe(ctx context.Context, key, domainID, chanID,
}

clientID := authnRes.GetId()
authzRes, err := svc.channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to remove the channel authorization while unsubscribing ?

supermq/coap/adapter.go

Lines 116 to 128 in 69b849c

authzRes, err := svc.channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{
DomainId: domainID,
ClientId: authnRes.GetId(),
ClientType: policies.ClientType,
Type: uint32(connections.Subscribe),
ChannelId: chanID,
})
if err != nil {
return errors.Wrap(svcerr.ErrAuthorization, err)
}
if !authzRes.Authorized {
return svcerr.ErrAuthorization
}

DomainId: domainID,
ClientId: clientID,
ClientType: policies.ClientType,
Type: uint32(connections.Subscribe),
ChannelId: chanID,
})
if err != nil {
return errors.Wrap(svcerr.ErrAuthorization, err)
}
if !authzRes.Authorized {
return svcerr.ErrAuthorization
}

subject := messaging.EncodeTopic(domainID, chanID, subtopic)
authzc := newAuthzClient(clientID, domainID, chanID, subtopic, svc.channels, c)
Expand All @@ -140,21 +112,6 @@ func (svc *adapterService) Unsubscribe(ctx context.Context, key, domainID, chanI
if !authnRes.Authenticated {
return svcerr.ErrAuthentication
}

authzRes, err := svc.channels.Authorize(ctx, &grpcChannelsV1.AuthzReq{
DomainId: domainID,
ClientId: authnRes.GetId(),
ClientType: policies.ClientType,
Type: uint32(connections.Subscribe),
ChannelId: chanID,
})
if err != nil {
return errors.Wrap(svcerr.ErrAuthorization, err)
}
if !authzRes.Authorized {
return svcerr.ErrAuthorization
}

subject := messaging.EncodeTopic(domainID, chanID, subtopic)

return svc.pubsub.Unsubscribe(ctx, token, subject)
Expand Down
183 changes: 183 additions & 0 deletions coap/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package coap

import (
"context"
"fmt"
"log/slog"
"net/http"
"strings"

mgate "github.com/absmach/mgate/pkg/coap"
"github.com/absmach/mgate/pkg/session"
grpcChannelsV1 "github.com/absmach/supermq/api/grpc/channels/v1"
grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1"
smqauthn "github.com/absmach/supermq/pkg/authn"
"github.com/absmach/supermq/pkg/connections"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
"github.com/absmach/supermq/pkg/messaging"
"github.com/absmach/supermq/pkg/policies"
)

var _ session.Handler = (*handler)(nil)

// Log message formats.
const (
subscribedInfoFmt = "subscribed with client_id %s to topics %s"
publishedInfoFmt = "published with client_id %s to the topic %s"
)

// Error wrappers for COAP errors.
var (
errClientNotInitialized = errors.New("client is not initialized")
errMissingTopicPub = errors.New("failed to publish due to missing topic")
errMissingTopicSub = errors.New("failed to subscribe due to missing topic")
errFailedPublish = errors.New("failed to publish")
)

type handler struct {
clients grpcClientsV1.ClientsServiceClient
channels grpcChannelsV1.ChannelsServiceClient
logger *slog.Logger
parser messaging.TopicParser
}

// NewHandler creates new Handler entity.
func NewHandler(logger *slog.Logger, clients grpcClientsV1.ClientsServiceClient, channels grpcChannelsV1.ChannelsServiceClient, parser messaging.TopicParser) session.Handler {
return &handler{
logger: logger,
clients: clients,
channels: channels,
parser: parser,
}
}

// AuthConnect is called on device connection,
// prior forwarding to the coap server.
func (h *handler) AuthConnect(ctx context.Context) error {
return nil
}

// AuthPublish is called on device publish,
// prior forwarding to the coap server.
func (h *handler) AuthPublish(ctx context.Context, topic *string, payload *[]byte) error {
if topic == nil {
return errMissingTopicPub
}
s, ok := session.FromContext(ctx)
if !ok {
return errClientNotInitialized
}

domainID, channelID, _, topicType, err := h.parser.ParsePublishTopic(ctx, *topic, true)
if err != nil {
return mgate.NewCOAPProxyError(http.StatusBadRequest, errors.Wrap(errFailedPublish, err))
}

clientID, err := h.authAccess(ctx, string(s.Password), domainID, channelID, connections.Publish, topicType)
if err != nil {
return err
}
s.Username = clientID

return nil
}

// AuthSubscribe is called on device publish,
// prior forwarding to the COAP broker.
func (h *handler) AuthSubscribe(ctx context.Context, topics *[]string) error {
s, ok := session.FromContext(ctx)
if !ok {
return errClientNotInitialized
}
if topics == nil || *topics == nil {
return errMissingTopicSub
}

for _, topic := range *topics {
domainID, channelID, _, topicType, err := h.parser.ParseSubscribeTopic(ctx, topic, true)
if err != nil {
return err
}
if _, err := h.authAccess(ctx, string(s.Password), domainID, channelID, connections.Subscribe, topicType); err != nil {
return err
}
}
return nil
}

// Connect - after client successfully connected.
func (h *handler) Connect(ctx context.Context) error {
return nil
}

// Publish - after client successfully published.
func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) error {
s, ok := session.FromContext(ctx)
if !ok {
return errClientNotInitialized
}

if len(*payload) == 0 {
h.logger.Warn("Empty payload, not publishing to broker", slog.String("client_id", s.Username))
return nil
}

h.logger.Info(fmt.Sprintf(publishedInfoFmt, s.Username, *topic))

return nil
}

// Subscribe - after client successfully subscribed.
func (h *handler) Subscribe(ctx context.Context, topics *[]string) error {
s, ok := session.FromContext(ctx)
if !ok {
return errClientNotInitialized
}
h.logger.Info(fmt.Sprintf(subscribedInfoFmt, s.Username, strings.Join(*topics, ",")))
return nil
}

// Unsubscribe - after client unsubscribed.
func (h *handler) Unsubscribe(ctx context.Context, topics *[]string) error {
return nil
}

// Disconnect - connection with broker or client lost.
func (h *handler) Disconnect(ctx context.Context) error {
return nil
}

func (h *handler) authAccess(ctx context.Context, secret, domainID, chanID string, msgType connections.ConnType, topicType messaging.TopicType) (string, error) {
authnRes, err := h.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{Token: smqauthn.AuthPack(smqauthn.DomainAuth, domainID, secret)})
if err != nil {
return "", mgate.NewCOAPProxyError(http.StatusUnauthorized, svcerr.ErrAuthentication)
}
if !authnRes.Authenticated {
return "", mgate.NewCOAPProxyError(http.StatusUnauthorized, svcerr.ErrAuthentication)
}

if topicType == messaging.HealthType {
return authnRes.GetId(), nil
}

ar := &grpcChannelsV1.AuthzReq{
Type: uint32(msgType),
ClientId: authnRes.GetId(),
ClientType: policies.ClientType,
ChannelId: chanID,
DomainId: domainID,
}
res, err := h.channels.Authorize(ctx, ar)
if err != nil {
return "", mgate.NewCOAPProxyError(http.StatusUnauthorized, errors.Wrap(svcerr.ErrAuthentication, err))
}
if !res.GetAuthorized() {
return "", mgate.NewCOAPProxyError(http.StatusUnauthorized, svcerr.ErrAuthentication)
}

return authnRes.GetId(), nil
}
7 changes: 5 additions & 2 deletions docker/.env
Original file line number Diff line number Diff line change
Expand Up @@ -398,11 +398,14 @@ SMQ_MQTT_ADAPTER_CACHE_MAX_COST=1048576
SMQ_MQTT_ADAPTER_CACHE_BUFFER_ITEMS=64

### CoAP
## If enabled run make all inside docker/ssl directory to generate the DTLS certs
SMQ_COAP_DTLS=
SMQ_COAP_ADAPTER_LOG_LEVEL=debug
SMQ_COAP_ADAPTER_HOST=coap-adapter
SMQ_COAP_ADAPTER_PORT=5683
SMQ_COAP_ADAPTER_SERVER_CERT=
SMQ_COAP_ADAPTER_SERVER_KEY=
SMQ_COAP_ADAPTER_SERVER_CERT_FILE=${SMQ_COAP_DTLS:+./ssl/certs/coap-server.crt}
SMQ_COAP_ADAPTER_SERVER_KEY_FILE=${SMQ_COAP_DTLS:+./ssl/certs/coap-server.key}
SMQ_COAP_ADAPTER_SERVER_CA_FILE=${SMQ_COAP_DTLS:+./ssl/certs/coap-server-ca.crt}
SMQ_COAP_ADAPTER_HTTP_HOST=coap-adapter
SMQ_COAP_ADAPTER_HTTP_PORT=5683
SMQ_COAP_ADAPTER_HTTP_SERVER_CERT=
Expand Down
Loading
Loading