From 216f65f42dfae8cf1daa384f4dc368dea00d7f02 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Wed, 31 Jan 2024 14:01:46 +0000 Subject: [PATCH] Attempt to configure CA for insecure device during onboarding to cloud It is expected that the insecure device is within the trust network, and the client application is behind the proxy that authorizes access --- .github/workflows/release.yml | 4 +- Makefile | 2 +- service/grpc/onboardDevice.go | 54 +++++++++++++++++---- service/grpc/onboardDevice_internal_test.go | 39 +++++++++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b3fe5b5..ea89f58e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,9 +58,9 @@ jobs: id: args run: | if ${{ github.ref_type == 'tag' }} ; then - echo "args=release --rm-dist" >> $GITHUB_OUTPUT + echo "args=release --clean" >> $GITHUB_OUTPUT else - echo "args=release --rm-dist --skip-validate --skip-publish" >> $GITHUB_OUTPUT + echo "args=release --clean --skip=validate --skip=publish" >> $GITHUB_OUTPUT fi - name: Run GoReleaser diff --git a/Makefile b/Makefile index b1930e35..9b2cf051 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ inject-web: $(CLIENT_APPLICATION_BINARY_PATH) .PHONY: inject-web build: - UI_FILE=$(UI_FILE) UI_SEPARATOR=$(UI_SEPARATOR) goreleaser build --rm-dist --single-target --skip-validate + UI_FILE=$(UI_FILE) UI_SEPARATOR=$(UI_SEPARATOR) goreleaser build --clean --single-target --skip=validate .PHONY: build test: env diff --git a/service/grpc/onboardDevice.go b/service/grpc/onboardDevice.go index 0a9d3ba3..e35c48bd 100644 --- a/service/grpc/onboardDevice.go +++ b/service/grpc/onboardDevice.go @@ -28,10 +28,10 @@ import ( "github.com/plgd-dev/device/v2/schema" "github.com/plgd-dev/device/v2/schema/acl" "github.com/plgd-dev/device/v2/schema/cloud" + "github.com/plgd-dev/device/v2/schema/credential" "github.com/plgd-dev/device/v2/schema/maintenance" "github.com/plgd-dev/device/v2/schema/softwareupdate" "github.com/plgd-dev/kit/v2/security" - "github.com/plgd-dev/kit/v2/strings" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -151,6 +151,38 @@ func onboardSecureDevice(ctx context.Context, dev *device, links schema.Resource }) } +func insecureAddCredentials(ctx context.Context, cloudID string, dev *device, links schema.ResourceLinks, cert string) error { + links = links.GetResourceLinks(credential.ResourceType) + if len(links) == 0 { + // add credential resource is not supported by device + return nil + } + _, err := security.ParseX509FromPEM([]byte(cert)) + if err != nil { + return fmt.Errorf("cannot parse CA for device %v: %w", dev.DeviceID(), err) + } + link := links[0] + link.Endpoints = link.Endpoints.FilterUnsecureEndpoints() + setCaCredential := credential.CredentialUpdateRequest{ + Credentials: []credential.Credential{ + { + Subject: cloudID, + Type: credential.CredentialType_ASYMMETRIC_SIGNING_WITH_CERTIFICATE, + Usage: credential.CredentialUsage_TRUST_CA, + PublicData: &credential.CredentialPublicData{ + DataInternal: cert, + Encoding: credential.CredentialPublicDataEncoding_PEM, + }, + }, + }, + } + err = dev.UpdateResource(ctx, link, setCaCredential, nil, coap.WithDeviceID(dev.DeviceID())) + if err != nil { + return fmt.Errorf("cannot add CA to credential resource %v of device %v: %w", link.Href, dev.DeviceID(), err) + } + return nil +} + func onboardInsecureDevice(ctx context.Context, dev *device, links schema.ResourceLinks, req *pb.OnboardDeviceRequest) error { switch { case req.GetAuthorizationProviderName() == "": @@ -160,18 +192,20 @@ func onboardInsecureDevice(ctx context.Context, dev *device, links schema.Resour case req.GetCoapGatewayAddress() == "": return fmt.Errorf("invalid URL") } - var link schema.ResourceLink - - for _, l := range links { - if strings.SliceContains(l.ResourceTypes, cloud.ResourceType) { - link = l - break - } - } - if link.Href == "" { + cloudLinks := links.GetResourceLinks(cloud.ResourceType) + if len(cloudLinks) == 0 { return fmt.Errorf("could not resolve cloud resource link of device %s", dev.DeviceID()) } + link := cloudLinks[0] link.Endpoints = link.Endpoints.FilterUnsecureEndpoints() + + if len(req.GetCertificateAuthorities()) > 0 { + err := insecureAddCredentials(ctx, req.GetHubId(), dev, links, req.GetCertificateAuthorities()) + if err != nil { + return err + } + } + err := dev.UpdateResource(ctx, link, cloud.ConfigurationUpdateRequest{ AuthorizationProvider: req.GetAuthorizationProviderName(), AuthorizationCode: req.GetAuthorizationCode(), diff --git a/service/grpc/onboardDevice_internal_test.go b/service/grpc/onboardDevice_internal_test.go index 7503279b..3f66ab4f 100644 --- a/service/grpc/onboardDevice_internal_test.go +++ b/service/grpc/onboardDevice_internal_test.go @@ -18,14 +18,19 @@ package grpc import ( "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "strings" "testing" "time" "github.com/plgd-dev/client-application/pb" "github.com/plgd-dev/device/v2/client/core" + "github.com/plgd-dev/device/v2/pkg/security/generateCertificate" "github.com/plgd-dev/device/v2/schema" "github.com/plgd-dev/device/v2/schema/cloud" + "github.com/plgd-dev/device/v2/schema/credential" "github.com/stretchr/testify/require" ) @@ -51,3 +56,37 @@ func TestOnboardInsecureDevice(t *testing.T) { require.Error(t, err) require.True(t, strings.Contains(err.Error(), "could not set cloud resource of device")) } + +func TestOnboardInsecureDeviceWithCA(t *testing.T) { + // we don't have a insecure device simulator for this test, so we create a fake device + // and try to onboard it + ctx, cancel := context.WithTimeout(context.Background(), time.Second*8) + defer cancel() + dev := device{Device: &core.Device{}} + links := schema.ResourceLinks{ + { + Href: cloud.ResourceURI, + ResourceTypes: []string{cloud.ResourceType}, + }, + { + Href: credential.ResourceURI, + ResourceTypes: []string{credential.ResourceType}, + }, + } + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + caPEM, err := generateCertificate.GenerateRootCA(generateCertificate.Configuration{}, key) + require.NoError(t, err) + + err = onboardInsecureDevice(ctx, &dev, links, &pb.OnboardDeviceRequest{ + DeviceId: "devId", + CoapGatewayAddress: "coaps+tcp://localhost:5684", + AuthorizationCode: "authCode", + AuthorizationProviderName: "authProviderName", + HubId: "hubId", + CertificateAuthorities: string(caPEM), + }) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "cannot add CA to credential resource")) +}