Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Overview

This Guidance uses AWS IoT Core and Amazon Kinesis Video Streams APIs to delete a device and its associated resources created by the StartCreateDevice workflow.

OperationIoT/KVS ActionRespective CreateDevice Workflow Step
Delete thing shadowsiot:DeleteThingShadowSetLoggerConfig
Detach cert from thingiot:DetachThingPrincipalCreateDevice
Detach policies from certiot:DetachPolicyAttachKvsAccessToCert
Delete KVS streamkinesisvideo:DeleteStreamKVSResourceCreateLambda
Delete KVS channelskinesisvideo:DeleteSignalingChannelKVSResourceCreateLambda
Delete thingiot:DeleteThingCreateDevice
Delete certificateiot:DeleteCertificateStartCreateDevice API call pre-req

It is recommended to delete the X.509 certificate (certificateId argument passed into StartCreateDevice) after detaching it from the device Ref. The reason why StartCreateDevice is not recommended to handle creating the keys and certificate within the workflow is to avoid storage of keys/certs in DDB. Note: orphan certificates do not incur any costs; the recommendation is purely from security standpoint.

Assumptions:

  1. Device has been created with StartCreateDevice.

Instructions to delete the device

  1. Execute the Java code below

[Tip] Customers can choose to 1/embed the Java code provided here to an existing application or 2/implement the Java code following this guidance’s server-less pattern of APIGW → Lambda for synchronous operation or APIGW -> Lambda -> DDB -> StepFunction for asynchronous operations.

import software.amazon.awssdk.services.iot.IotClient;
import software.amazon.awssdk.services.iot.model.*;
import software.amazon.awssdk.services.iotdataplane.IotDataPlaneClient;
import software.amazon.awssdk.services.iotdataplane.model.DeleteThingShadowRequest;
import software.amazon.awssdk.services.iotdataplane.model.GetThingShadowRequest;
import software.amazon.awssdk.services.iotdataplane.model.IotDataPlaneException;
import software.amazon.awssdk.services.kinesisvideo.KinesisVideoClient;
import software.amazon.awssdk.services.kinesisvideo.model.DeleteSignalingChannelRequest;
import software.amazon.awssdk.services.kinesisvideo.model.DescribeSignalingChannelRequest;
import software.amazon.awssdk.services.kinesisvideo.model.DescribeSignalingChannelResponse;
import software.amazon.awssdk.services.kinesisvideo.model.DeleteStreamRequest;
import software.amazon.awssdk.services.kinesisvideo.model.DescribeStreamRequest;
import software.amazon.awssdk.services.kinesisvideo.model.DescribeStreamResponse;
import software.amazon.awssdk.services.kinesisvideo.model.KinesisVideoException;
import software.amazon.awssdk.arns.Arn;

import java.util.Arrays;
import java.util.List;

public void deleteDevice(
    final String thingName,
    final IotClient iotClient,
    final IotDataPlaneClient iotDataPlaneClient,
    final KinesisVideoClient kvsClient
) {
    // Validate if device exists
    System.out.println("Checking if device exist.");
    try {
        iotClient.describeThing(DescribeThingRequest.builder().thingName(deviceId).build());
    } catch (ResourceNotFoundException e){
        System.out.printf("Thing %s doesn't exist.%n", deviceId);
        return;
    }

    System.out.println("Deleting thing shadows");

    // null represents classic shadow which does not have a name
    // If more shadows are created for additional features, add them here
    List<String> shadowNames = Arrays.asList(null, "provision", "snapshot", "videoEncoder");

    for (String shadowName: shadowNames) {
        try {
            GetThingShadowRequest getThingShadowRequest = GetThingShadowRequest.builder()
                .thingName(deviceId)
                .shadowName(shadowName)
                .build();
            iotDataPlaneClient.getThingShadow(getThingShadowRequest);
            // if ResourceNotFoundException is not thrown, shadow exists
            try {
                DeleteThingShadowRequest deleteThingShadowRequest = DeleteThingShadowRequest.builder()
                    .thingName(deviceId)
                    .shadowName(shadowName)
                    .build();
                iotDataPlaneClient.deleteThingShadow(deleteThingShadowRequest);
            } catch (IotDataPlaneException e) {
                System.out.println(e);
            }
        } catch (software.amazon.awssdk.services.iotdataplane.model.ResourceNotFoundException e) {
            // shadow does not exist, no-op
        }
    }

    ListThingPrincipalsRequest listThingPrincipalsRequest = ListThingPrincipalsRequest.builder()
        .thingName(deviceId)
        .build();
    try {
        List<String> principals = iotClient.listThingPrincipals(listThingPrincipalsRequest).principals();

        System.out.println("Detaching cert from device");

        for (String principal: principals) {
            DetachThingPrincipalRequest detachThingPrincipalRequest = DetachThingPrincipalRequest.builder()
                .principal(principal)
                .thingName(deviceId)
                .build();
            iotClient.detachThingPrincipal(detachThingPrincipalRequest);

            Arn arn = Arn.fromString(principal);
            ArnResource arnResource = arn.resource();
            if (arnResource.resourceType().orElse("").equals("cert")) {
                ListAttachedPoliciesRequest listAttachedPoliciesRequest = ListAttachedPoliciesRequest.builder()
                    .target(principal)
                    .build();
                List<Policy> policies = iotClient.listAttachedPolicies(listAttachedPoliciesRequest).policies();

                System.out.println("Detaching policies from cert");

                for (Policy policy: policies) {
                    DetachPolicyRequest detachPolicyRequest = DetachPolicyRequest.builder()
                        .policyName(policy.policyName())
                        .target(principal)
                        .build();
                    iotClient.detachPolicy(detachPolicyRequest);
                }

                // Certificate must be deactivated before being deleted
                System.out.println("Deactivating cert");

                String certificateId = arnResource.resource();
                UpdateCertificateRequest updateCertificateRequest = UpdateCertificateRequest.builder()
                    .certificateId(certificateId)
                    .newStatus(CertificateStatus.INACTIVE)
                    .build();
                iotClient.updateCertificate(updateCertificateRequest);

                System.out.println("Deleting cert");

                DeleteCertificateRequest deleteCertificateRequest = DeleteCertificateRequest.builder()
                    .certificateId(certificateId)
                    .build();
                iotClient.deleteCertificate(deleteCertificateRequest);
            }
        }
    } catch (IotException e) {
        System.out.println(e);
    }

    System.out.println("Deleting KVS stream");

    // By default, only 1 KVS stream (name=deviceId) is created.
    // If more are created, add them here
    try {
        DescribeStreamRequest describeStreamRequest = DescribeStreamRequest.builder()
                .streamName(deviceId)
                .build();
        DescribeStreamResponse describeStreamResponse = kvsClient.describeStream(describeStreamRequest);
        DeleteStreamRequest deleteStreamRequest = DeleteStreamRequest.builder()
            .streamARN(describeStreamResponse.streamInfo().streamARN())
            .build();
        kvsClient.deleteStream(deleteStreamRequest);
    } catch (KinesisVideoException e) {
        System.out.println(e);
    }

    System.out.println("Deleting KVS signaling channels");

    // By default, only 2 KVS signaling channels are created with deviceId and the following suffixes.
    // If more are created, add them here
    List<String> channelSuffixes = List.of("%s-LiveStreamSignalingChannel", "%s-PlaybackSignalingChannel");

    for (String suffix: channelSuffixes) {  
        String signalingChannelName = String.format(suffix, deviceId);  
        try {
            DescribeSignalingChannelRequest describeSignalingChannelRequest = DescribeSignalingChannelRequest.builder()
                .channelName(signalingChannelName)
                .build();
            DescribeSignalingChannelResponse describeSignalingChannelResponse = kvsClient.describeSignalingChannel(describeSignalingChannelRequest);
            DeleteSignalingChannelRequest deleteSignalingChannelRequest = DeleteSignalingChannelRequest.builder()
                .channelARN(describeSignalingChannelResponse.channelInfo().channelARN())
                .build();
            kvsClient.deleteSignalingChannel(deleteSignalingChannelRequest);
        } catch (KinesisVideoException e) {
            System.out.println(e);
        }
    }

    System.out.println("Deleting thing");
    DeleteThingRequest deleteThingRequest = DeleteThingRequest.builder()
        .thingName(deviceId)
        .build();
    try {
        iotClient.deleteThing(deleteThingRequest);
    } catch (IotException e) {
        System.out.println(e);
    }
}

Verify:

  1. Sign in to AWS IoT console. In the menu bar on the left under Manage, find All devices > Things. The device that was deleted should not be there.
  2. In the menu bar on the left under Manage, find Security > Certificates. The certificate used to create the device should not be there.
  3. Sign in to AWS KVS console. In the menu bar on the left, find Video streams. The stream for the device should not be there or the status should be “Deleting” (can take around 30 seconds to delete).
  4. In the menu bar on the left, find Signaling channels. The signaling channels for the device should not be there.