使用无服务器框架为多个 IoT 设备设置 AWS IoT

Setting up AWS IoT using Serverless Framework for Multiple IoT Devices

我的目标是使用 serverless framework 在 AWS 上创建一个系统,供多个 IoT 设备向 AWS IoT 发送 JSON 有效载荷,然后将其保存到 DynamoDB。

除了创建 EC2 服务器之外,我对使用 AWS 还很陌生,这是我使用 serverless framework 的第一个项目。

参考an example修改后的版本贴在下面

问题: 看来该示例仅用于连接到 AWS IoT 的 1 台设备,这是我从正在使用的硬编码 IoT Thing 证书得出的结论,例如

SensorPolicyPrincipalAttachmentCert:
  Type: AWS::IoT::PolicyPrincipalAttachment
  Properties:
    PolicyName: { Ref: SensorThingPolicy }
    Principal: ${{custom.iotCertificateArn}}

SensorThingPrincipalAttachmentCert:
  Type: "AWS::IoT::ThingPrincipalAttachment"
  Properties:
    ThingName: { Ref: SensorThing }
    Principal: ${self:custom.iotCertificateArn}

如果serverless.yml只配置了1个Thing这个结论是正确的,那么我们可以做哪些修改才能使用1个以上的Thing?

也许设置 serverless.yaml 之外的所有东西?这意味着只删除 SensorPolicyPrincipalAttachmentCertSensorThingPrincipalAttachmentCert?

另外,SensorThingPolicy中的Resource属性应该怎么设置呢?目前设置为"*",是不是太宽泛了?或者有什么方法可以限制为事物。

serverless.yml

service: garden-iot

provider:
name: aws
runtime: nodejs6.10
region: us-east-1

# load custom variables from a file
custom: ${file(./vars-dev.yml)}

resources:
Resources:
    LocationData:
    Type: AWS::DynamoDB::Table
    Properties:
        TableName: location-data-${opt:stage}
        AttributeDefinitions:
        - 
            AttributeName: ClientId
            AttributeType: S
        - 
            AttributeName: Timestamp
            AttributeType: S
        KeySchema:
        - 
            AttributeName: ClientId
            KeyType: HASH
        - 
            AttributeName: Timestamp
            KeyType: RANGE
        ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

    SensorThing:
    Type: AWS::IoT::Thing
    Properties:
        AttributePayload:
        Attributes:
            SensorType: soil

    SensorThingPolicy:
    Type: AWS::IoT::Policy
    Properties:
        PolicyDocument:
        Version: "2012-10-17"
        Statement:
            - Effect: Allow
            Action: ["iot:Connect"]
            Resource: ["${self:custom.sensorThingClientResource}"]
            - Effect: "Allow"
            Action: ["iot:Publish"]
            Resource: ["${self:custom.sensorThingSoilTopicResource}"]

    SensorPolicyPrincipalAttachmentCert:
    Type: AWS::IoT::PolicyPrincipalAttachment
    Properties:
        PolicyName: { Ref: SensorThingPolicy }
        Principal: ${{custom.iotCertificateArn}}

    SensorThingPrincipalAttachmentCert:
    Type: "AWS::IoT::ThingPrincipalAttachment"
    Properties:
        ThingName: { Ref: SensorThing }
        Principal: ${self:custom.iotCertificateArn}

IoTRole:
Type: AWS::IAM::Role
Properties:
    AssumeRolePolicyDocument:
    Version: "2012-10-17"
    Statement:
        -
        Effect: Allow
        Principal:
            Service:
            - iot.amazonaws.com
        Action:
            - sts:AssumeRole

IoTRolePolicies:
Type: AWS::IAM::Policy
Properties:
    PolicyName: IoTRole_Policy
    PolicyDocument:
    Version: "2012-10-17"
    Statement:
        -
        Effect: Allow
        Action:
            - dynamodb:PutItem
        Resource: "*"
        -
        Effect: Allow
        Action:
            - lambda:InvokeFunction
        Resource: "*"
    Roles: [{ Ref: IoTRole }]

编辑 05/09/2018: 我找到了这个博客 post,它很好地描述了我的方法:Ensure Secure Communication with AWS IoT Core Using the Certificate Vending Machine Reference Application

--

你可以看看Just-in-Time Provisioning or build your own solution based on Programmatic Provisioning

我已经多次处理过这个主题,并且不得不意识到它在很大程度上取决于用例,这更有意义。安全性也是需要关注的一个方面。您不想让 public API 负责整个 Internet 都可以访问的 JIT 设备注册。

一个简单的基于程序化配置的场景可能如下所示:您构建一个东西(可能是一个传感器),它应该能够连接到 AWS IoT 并有一个内部配置过程。

简单的配置过程:

  1. 建造的东西
  2. 东西有序列号
  3. 事物通过内部服务器自行注册

服务器上的注册码运行可能是这样的(JS + AWS JS SDK):

// Modules
const AWS = require('aws-sdk')

// AWS
const iot = new AWS.Iot({ region: process.env.region })

// Config
const templateBodyJson = require('./register-thing-template-body.json')

// registerThing
const registerThing = async ({ serialNumber = null } = {}) => {
  if (!serialNumber) throw new Error('`serialNumber` required!')

  const {
    certificateArn = null,
    certificateId = null,
    certificatePem = null,
    keyPair: {
      PrivateKey: privateKey = null,
      PublicKey: publicKey = null
    } = {}
  } = await iot.createKeysAndCertificate({ setAsActive: true }).promise()
  const registerThingParams = {
    templateBody: JSON.stringify(templateBodyJson),
    parameters: {
      ThingName: serialNumber,
      SerialNumber: serialNumber,
      CertificateId: certificateId
    }
  }
  const { resourceArns = null } = await iot.registerThing(registerThingParams).promise()

  return {
    certificateArn,
    certificateId,
    certificatePem,
    privateKey,
    publicKey,
    resourceArns
  }
}

const unregisterThing = async ({ serialNumber = null } = {}) => {
  if (!serialNumber) throw new Error('`serialNumber` required!')

  try {
    const thingName = serialNumber
    const { principals: thingPrincipals } = await iot.listThingPrincipals({ thingName }).promise()
    const certificates = thingPrincipals.map((tp) => ({ certificateId: tp.split('/').pop(), certificateArn: tp }))

    for (const { certificateId, certificateArn } of certificates) {
      await iot.detachThingPrincipal({ thingName, principal: certificateArn }).promise()
      await iot.updateCertificate({ certificateId, newStatus: 'INACTIVE' }).promise()
      await iot.deleteCertificate({ certificateId, forceDelete: true }).promise()
    }

    await iot.deleteThing({ thingName }).promise()

    return {
      deleted: true,
      thingPrincipals
    }
  } catch (err) {
    // Already deleted!
    if (err.code && err.code === 'ResourceNotFoundException') {
      return {
        deleted: true,
        thingPrincipals: []
      }
    }

    throw err
  }
}

register-thing-template-body.json:

{
  "Parameters": {
     "ThingName": {
       "Type": "String"
     },
     "SerialNumber": {
       "Type": "String"
     },
     "CertificateId": {
       "Type": "String"
     }
  },
  "Resources": {
    "thing": {
      "Type": "AWS::IoT::Thing",
      "Properties": {
        "ThingName": {
          "Ref": "ThingName"
        },
        "AttributePayload": {
          "serialNumber": {
            "Ref": "SerialNumber"
          }
        },
        "ThingTypeName": "NewDevice",
        "ThingGroups": ["NewDevices"]
      }
    },
    "certificate": {
      "Type": "AWS::IoT::Certificate",
      "Properties": {
        "CertificateId": {
          "Ref": "CertificateId"
        }
      }
    },
    "policy": {
      "Type": "AWS::IoT::Policy",
      "Properties": {
        "PolicyName": "DefaultNewDevicePolicy"
      }
    }
  }
}

确保您已准备好所有 "NewDevice" 事物类型、组和策略。还要记住 ThingName = SerialNumber(对于 unregisterThing 很重要)。