使用动态策略和证书创建 AWS IoT "things"

Creating AWS IoT "things" with policies and certificates on-the-fly

我将 NodeJS 与 AWS JS SDK and AWS IoT Device JS SDK 一起使用,以便在它连接到我的服务器后自动创建新事物并为其分配证书和策略。

我关注 "Just-in-Time Registration" article 是为了创建、注册和激活我的 CA 证书。据我所知,CA 证书已成功添加到 AWS IoT,激活并启用自动注册。

我不明白的是这一步是如何执行的(引用文章):

When a device attempts to connect with an X.509 certificate that is not known to AWS IoT but was signed by a CA that was registered with AWS IoT, the device certificate will be auto-registered by AWS IoT in a new PENDING_ACTIVATION state.

如何使 "attempt" 连接?由于我使用的是 aws-iot-device-sdk-js SDK,使用手动创建的证书,我通常是这样连接我的设备的:

const device = deviceModule.device({
  host: 'myendpoint.iot.us-east-1.amazonaws.com',
  region: 'us-east-1',
  keyPath: `certs/${deviceID}.key`,
  certPath: `certs/${deviceID}.pem`,
  caPath: 'certs/rootCA.pem',
  clientId: deviceID,
  baseReconnectTimeMs: 4000,
  keepalive: 30,
  protocol: 'mqtts',
});

但现在我没有要包含在 keyPathcertPath 中的证书和密钥,没有它我无法实例化我的设备。

我尝试自己创建证书,使用来自 AWS SDK 的 createKeysAndCertificate(),将它们保存到磁盘,手动附加策略,手动附加主体甚至尝试手动将证书标记为 "active",有些东西按照这些思路:

iot.createThing({ thingName: deviceID }, (err, d) => {
  if (err) {
    console.log(err);
  } else {
    allThings[d.thingName] = d;
    iot.createKeysAndCertificate({ setAsActive: true }, (e, c) => {
      if (e) {
        console.log(e);
      } else {
        fs.writeFile(`certs/${deviceID}.pem`, c.certificatePem, (ef, f) => {
          if (ef) throw ef;
        });
        fs.writeFile(`certs/${deviceID}.key`, c.keyPair.PrivateKey, (ef, f) => {
          if (ef) throw ef;
        });
        iot.attachPrincipalPolicy({
          policyName: 'my-testing-policy',
          principal: c.certificateArn,
        }, (ee, cc) => {
          if (ee) {
            console.log(ee);
          } else {
            iot.attachThingPrincipal({
              principal: c.certificateArn,
              thingName: deviceID,
            }, (prerr, prdata) => {
              if (prerr) {
                console.log(prerr);
              } else {
                iot.acceptCertificateTransfer({
                  certificateId: c.certificateId,
                  setAsActive: true,
                }, (ce, cd) => {
                  if (err) {
                    console.log(err);
                  } else {
                    console.log('cert activated.');
                  }
                });
              }
            });
          }
        });
      }
    });
  }
});

但毕竟,当我尝试发布某些内容时出现错误:

Error: unable to get local issuer certificate
    at Error (native)
    at TLSSocket.<anonymous> (_tls_wrap.js:1092:38)
    at emitNone (events.js:86:13)
    at TLSSocket.emit (events.js:185:7)
    at TLSSocket._finishInit (_tls_wrap.js:610:8)
    at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:440:38)

我也尝试订阅特定主题,如上面同一篇文章中所述,aws/events/certificates/registered/e3f0a30...但我从未见过关于该主题的任何消息...

我在这里错过了什么?如何仅使用即时证书正确触发设备证书和私钥生成?

如果您想使用自己的 CA,则需要使用此 CA 并使用 openssl 创建证书。它不能通过使用 AWS SDK 来制作,因为您需要使用您的私人 rootCA 密钥,而 AWS 没有这个主要元素,aws 不应该有它。使用您自己的 CA 意味着大规模生产,否则您可以使用 AWS SDK 通过 AWS 的根 CA 创建证书。

使用即时注册

及时注册如何运作?

  1. 您需要创建自己的私有 rootCA 密钥,并且只有您应该拥有它。你必须保护它应有的安全。您创建此私钥的证书,AWS IoT 用于识别由您的私有根 CA 创建的 ccerts。
  2. 由于您需要使用 openssl 为您的设备创建证书,因此您应该使用 OS 说明或找到可以在 NodeJS 级别执行此操作的库。以下是使用 OS bash 脚本创建证书的示例:

    mkdir /iot/certsTemp cd /iot/certsTemp openssl genrsa -out .key 2048 openssl req -new -key .key -out .csr -subj "/C=MX/ST=CDMX/L=CDMX/O=CompanyX/OU=IoT/CN=IoT" openssl x509 -req -in .csr -CA /iot/CACerts/CACertificate.pem -CAkey /iot/CACerts/CACertificate.key -CAcreateserial -out .crt -days 1850 -sha256 cat .crt /iot/CACerts/CACertificate.pem > -CA.crt cat .crt

并且可以在 NodeJs 中使用如下方式调用此指令:

var sh = spawn('sh', ['bash/generateDeviceCerts.sh', deviceId]);
var pem;
sh.stdout.on('data', (data) => {
    if (data.indexOf('-----BEGIN CERTIFICATE-----') == 0) {
        pem = data.toString();
    }
});
sh.stderr.on('data', (data) => {
    //console.log(`cert stderr: ${data}`);
});
sh.on('close', (code) => {
    if (code == 0) {
        me.uploadCertificatesToS3(deviceId);
    }
});
  1. 获得此证书后,每台设备只能使用一对。设备第一次尝试连接时,AWS 检测到证书是使用用户的 rootCA 创建的,您可以使用 IoT 规则引擎捕获此事件以侦听主题并调用 Lambda。主题的最后一部分是您在 AWS IoT
  2. 中的 rootCA 证书 ID

lambda 看起来像:

var AWS = require('aws-sdk');

exports.handler = function(event, context, callback) {

var region = "us-east-1";

var accountId = event.awsAccountId.toString().trim();

var iot = new AWS.Iot({
    'region': region,
    apiVersion: '2015-05-28'
});
var certificateId = event.certificateId.toString().trim();

var certificateARN = `arn:aws:iot:${region}:${accountId}:cert/${certificateId}`;
var policyName = < Policy name > ;
//Asign IoT Policy to certificate
iot.attachPrincipalPolicy({
    policyName: policyName,
    principal: certificateARN
}, (err, data) => {
    if (err && (!err.code || err.code !== 'ResourceAlreadyExistsException')) {
        callback(err, data);
        return;
    }
    //Active the certificate
    iot.updateCertificate({
        certificateId: certificateId,
        newStatus: 'ACTIVE'
    }, (err, data) => {
        if (err) callback(err, data);
        else iot.createThing(params, function(err, data) {//Create a thing for this certificate
            if (err) callback(err, null);
            else {
                var params = {
                    principal: certificateARN,
                    thingName: <NAME>
                };
                //Attach certificate to this thing
                iot.attachThingPrincipal(params, function(err, data) {
                    if (err) callback(err, null);
                    else callback(null, "SUCCESS");
                });
            }
        });
    });
});
}

这就是全部。设备第一次尝试连接时会失败,但会 运行 lambda 中的此脚本,一旦成功完成,下次设备尝试连接时它应该可以工作,因为证书已注册并处于活动状态。

使用 SDK 和 Lambda。

如果您没有量产设备,我建议您使用 SDK 和 Lambda 来执行此操作,lambda 代码如下所示:

var AWS = require('aws-sdk');

exports.handler = function(event, context, callback) {

const region = "us-east-1";
const accountId = < YOUR ACCOUNT ID > ;
const BUCKET = < YOUR BUCKET > ;

var certificateARN;

var iot = new AWS.Iot({
    'region': region,
    apiVersion: '2015-05-28'
});

event.data.id = getId();
iot.createKeysAndCertificate({
    setAsActive: true
}, function(err, data) {
    if (err) callback(err, null);
    else {
        certificateARN = `arn:aws:iot:${region}:${accountId}:cert/${data.certificateId}`;
        uploadCertificatesToS3(event.data.id, data);
        iot.attachPrincipalPolicy({
            policyName: < YOUR IOT POLICY NAME > ,
            principal: certificateARN
        }, (err, data) => {
            if (err) callback(err, data);
            else {
                var thingName = event.data.id;
                iot.createThing({
                    thingName: thingName,
                    attributePayload: {
                        attributes: {},
                        merge: false
                    }
                }, function(err, data) {
                    if (err) callback(err, null);
                    else {
                        iot.attachThingPrincipal({
                            principal: certificateARN,
                            thingName: thingName
                        }, function(err, data) {
                            if (err) callback(err, null);
                            else callback(null, event.data);
                        });
                    }
                });
            }
        });
    }
});

function getId() {
    return Math.trunc(new Date().getTime() / 1000) + '-' + Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}

function uploadCertificatesToS3(deviceId, certsData) {
    return Promise.all([
        new Promise(function(resolve, reject) {
            var base64data = new Buffer(certsData.certificatePem, 'binary');
            var s3 = new AWS.S3();
            s3.putObject({
                Bucket: BUCKET,
                Key: `${event.data.project}/devices-certificates/${deviceId}/${deviceId}.crt`,
                Body: base64data
            }, function(err, data) {
                if (err) console.log("Error S3", deviceId);
            });
        }),
        new Promise(function(resolve, reject) {
            var base64data = new Buffer(certsData.keyPair.PrivateKey, 'binary');
            var s3 = new AWS.S3();
            s3.putObject({
                Bucket: BUCKET,
                Key: `${event.data.project}/devices-certificates/${deviceId}/${deviceId}.key`,
                Body: base64data
            }, function(err, data) {
                if (err) console.log("Error S3", deviceId);
            });
        }),
        new Promise(function(resolve, reject) {
            var base64data = new Buffer(certsData.keyPair.PublicKey, 'binary');
            var s3 = new AWS.S3();
            s3.putObject({
                Bucket: BUCKET,
                Key: `${event.data.project}/devices-certificates/${deviceId}/${deviceId}.public.key`,
                Body: base64data
            }, function(err, data) {
                if (err) console.log("Error S3", deviceId);
            });
        })
    ]);
}
}

这将使用 AWS rootCA 创建证书,将它们保存在 s3 中,创建事物,将策略附加到证书并将证书附加到事物,所有这些都在一个 lambda 函数中完成。您可以使用这样的东西调用此 lambda:

var AWS = require("aws-sdk");
AWS.config.update({
   region: "us-east-1"
});

new AWS.Lambda().invoke({
    FunctionName: 'createDevice',
    InvocationType: "RequestResponse",
    LogType: "Tail",
    Payload: JSON.stringify({
        data: {
            name: < NAME > ,
            description: < DESCRIPTION > ,
            project: < PROJECT >
        }
    })
}, function(err, data) {
    if (err) console.log(err);
    else console.log(JSON.parse(data.Payload));
});

在“RequestResponse”lambda 执行模式下,当您在 lambda 中调用 "callback" 函数时将执行回调,您可以从该 lambda 接收数据。

建议 您应该创建有限制的 IoT 策略并使用我在此处描述的所有元素,您需要配置正确的权限。

此致,