Skip to content

Commit

Permalink
Add X509 sample (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
TwistedTwigleg authored Mar 22, 2023
1 parent 3773593 commit 1bf923e
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ env:
CI_IOT_CONTAINERS: ${{ secrets.AWS_CI_IOT_CONTAINERS }}
CI_PUBSUB_ROLE: ${{ secrets.AWS_CI_PUBSUB_ROLE }}
CI_COGNITO_ROLE: ${{ secrets.AWS_CI_COGNITO_ROLE }}
CI_X509_ROLE: ${{ secrets.AWS_CI_X509_ROLE }}
CI_CUSTOM_AUTHORIZER_ROLE: ${{ secrets.AWS_CI_CUSTOM_AUTHORIZER_ROLE }}
CI_SHADOW_ROLE: ${{ secrets.AWS_CI_SHADOW_ROLE }}
CI_JOBS_ROLE: ${{ secrets.AWS_CI_JOBS_ROLE }}
Expand Down Expand Up @@ -212,6 +213,14 @@ jobs:
- name: run Cognito Connect sample
run: |
python3 ./aws-iot-device-sdk-js-v2/utils/run_sample_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_cognito_connect_cfg.json
- name: configure AWS credentials (X509)
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ env.CI_X509_ROLE }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: run X509 sample
run: |
python3 ./aws-iot-device-sdk-js-v2/utils/run_sample_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_x509_connect_cfg.json
- name: configure AWS credentials (Custom Authorizer)
uses: aws-actions/configure-aws-credentials@v1
with:
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/ci_run_x509_connect_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"language": "Javascript",
"sample_file": "./aws-iot-device-sdk-js-v2/samples/node/x509_connect",
"sample_region": "us-east-1",
"sample_main_class": "",
"arguments": [
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--x509_cert",
"secret": "ci/PubSub/cert",
"filename": "tmp_certificate.pem"
},
{
"name": "--x509_key",
"secret": "ci/PubSub/key",
"filename": "tmp_key.pem"
},
{
"name": "--x509_endpoint",
"secret": "ci/X509/endpoint_credentials"
},
{
"name": "--x509_role_alias",
"secret": "ci/X509/alias"
},
{
"name": "--signing_region",
"data": "us-east-1"
},
{
"name": "--x509_thing_name",
"data": "CI_PubSub_Thing"
}
]
}
1 change: 1 addition & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* [Browser: Custom Authorizer Connect](./browser/custom_authorizer_connect/README.md)
* [Node: Cognito Connect](./node/cognito_connect/README.md)
* [Browser: Cognito Connect](./browser/pub_sub/README.md)
* [Node: X509 Connect](./node/x509_connect/README.md)
* [Node: Shadow](./node/shadow/README.md)
* [Node: Fleet Provisioning](./node/fleet_provisioning/README.md)
* [Node: Jobs](./node/jobs/README.md)
Expand Down
59 changes: 59 additions & 0 deletions samples/node/x509_connect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# x509 Credentials Provider Connect

[**Return to main sample list**](../../README.md)

This sample is similar to the [Basic Connect](../basic_connect/README.md), but the connection uses a X.509 certificate
to source the AWS credentials when connecting.

See the [Authorizing direct calls to AWS services using AWS IoT Core credential provider](https://docs.aws.amazon.com/iot/latest/developerguide/authorizing-direct-aws.html) page for instructions on how to setup the IAM roles, the trust policy for the IAM roles, how to setup the IoT Core Role alias, and how to get the credential provider endpoint for your AWS account.

Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended.

<details>
<summary>(see sample policy)</summary>
<pre>
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect"
],
"Resource": [
"arn:aws:iot:<b>region</b>:<b>account</b>:client/test-*"
]
},
{
"Effect":"Allow",
"Action":"iot:AssumeRoleWithCertificate",
"Resource":"arn:aws:iot:<b>region</b>:<b>account</b>:rolealias/<b>role-alias</b>"
}
]
}
</pre>

Replace with the following with the data from your AWS account:
* `<region>`: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`.
* `<account>`: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website.
* `<role-alias>`: The X509 role alias you created and wish to connect using.

Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id <client ID here>` to send the client ID your policy supports.

</details>

## How to run

To run the x509 Credentials Provider Connect sample use the following command:

``` sh
npm install
node dist/index.js --endpoint <endpoint> --signing_region <signing region> --x509_cert <path to x509 cert> --x509_endpoint <x509 credentials endpoint> --x509_key <path to x509 key> --x509_role_alias <alias> -x509_thing_name <thing name>
```

You can also pass a Certificate Authority file (CA) if your X509 certificate and key combination requires it:

``` sh
npm install
node dist/index.js --endpoint <endpoint> --signing_region <signing region> --x509_cert <path to x509 cert> --x509_endpoint <x509 credentials endpoint> --x509_key <path to x509 key> --x509_role_alias <alias> -x509_thing_name <thing name> --x509_ca_file <path to x509 CA file>
```
113 changes: 113 additions & 0 deletions samples/node/x509_connect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

import { mqtt } from 'aws-iot-device-sdk-v2';
import { iot } from 'aws-iot-device-sdk-v2';
import { http } from 'aws-iot-device-sdk-v2';
import { auth } from 'aws-iot-device-sdk-v2';
import { io } from 'aws-iot-device-sdk-v2';

type Args = { [index: string]: any };

const yargs = require('yargs');

// The relative path is '../../util/cli_args' from here, but the compiled javascript file gets put one level
// deeper inside the 'dist' folder
const common_args = require('../../../util/cli_args');

yargs.command('*', false, (yargs: any) => {
yargs.usage("Connect using X509.");
common_args.add_universal_arguments(yargs);
common_args.add_common_mqtt_arguments(yargs);
common_args.add_common_websocket_arguments(yargs, true);
common_args.add_x509_arguments(yargs);
common_args.add_proxy_arguments(yargs);
}, main).parse();

// Creates and returns a MQTT connection using a websockets and X509
function build_connection(argv: Args): mqtt.MqttClientConnection {

/**
* Pull data from the command line
*/
const input_endpoint = argv.endpoint;
const input_signing_region = argv.signing_region;
const input_ca_file = argv.ca_file;
const input_client_id = argv.client_id;

const input_proxy_host = argv.proxy_host;
const input_proxy_port = argv.proxy_port;

const input_x509_endpoint = argv.x509_endpoint;
const input_x509_thing_name = argv.x509_thing_name;
const input_x509_role_alias = argv.x509_role_alias;
const input_x509_cert = argv.x509_cert;
const input_x509_key = argv.x509_key;
const input_x509_ca_file = argv.x509_ca_file;

/**
* Set up and create the MQTT connection
*/

let x509_tls_ctx_opt = new io.TlsContextOptions();
x509_tls_ctx_opt.certificate_filepath = input_x509_cert;
x509_tls_ctx_opt.private_key_filepath = input_x509_key;
if (input_x509_ca_file != null && input_x509_ca_file != undefined) {
if (input_x509_ca_file.length > 0) {
x509_tls_ctx_opt.ca_filepath = input_x509_ca_file;
}
}
let x509_client_tls_ctx = new io.ClientTlsContext(x509_tls_ctx_opt);

let x509_config = {
endpoint: input_x509_endpoint,
thingName: input_x509_thing_name,
roleAlias: input_x509_role_alias,
tlsContext: x509_client_tls_ctx,
};
let x509_credentials_provider = auth.AwsCredentialsProvider.newX509(x509_config)

let config_builder = iot.AwsIotMqttConnectionConfigBuilder.new_with_websockets({
region: input_signing_region,
credentials_provider: x509_credentials_provider
});

if (input_proxy_host) {
config_builder.with_http_proxy_options(new http.HttpProxyOptions(input_proxy_host, input_proxy_port));
}

if (input_ca_file != null && input_ca_file != undefined) {
config_builder.with_certificate_authority_from_path(undefined, input_ca_file);
}

config_builder.with_clean_session(false);
config_builder.with_client_id(input_client_id || "test-" + Math.floor(Math.random() * 100000000));
config_builder.with_endpoint(input_endpoint);

// Create the MQTT connection from the configuration
const config = config_builder.build();
const client = new mqtt.MqttClient();
return client.new_connection(config);
}

async function main(argv: Args) {
common_args.apply_sample_arguments(argv);
const connection = build_connection(argv);

// force node to wait 20 seconds before killing itself, promises do not keep node alive
// ToDo: we can get rid of this but it requires a refactor of the native connection binding that includes
// pinning the libuv event loop while the connection is active or potentially active.
const timer = setInterval(() => { }, 20 * 1000);

console.log("Connecting...");
await connection.connect()
console.log("Connection completed.");
console.log("Disconnecting...");
await connection.disconnect()
console.log("Disconnect completed.");

// Allow node to die if the promise above resolved
clearTimeout(timer);
}
27 changes: 27 additions & 0 deletions samples/node/x509_connect/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "x509-connect",
"version": "1.0.0",
"description": "NodeJS IoT SDK v2 X509 Connect Sample",
"homepage": "https://github.com/aws/aws-iot-device-sdk-js-v2",
"repository": {
"type": "git",
"url": "git+https://github.com/aws/aws-iot-device-sdk-js-v2.git"
},
"contributors": [
"AWS SDK Common Runtime Team <[email protected]>"
],
"license": "Apache-2.0",
"main": "./dist/index.js",
"scripts": {
"tsc": "tsc",
"prepare": "npm run tsc"
},
"devDependencies": {
"@types/node": "^10.17.50",
"typescript": "^4.7.4"
},
"dependencies": {
"aws-iot-device-sdk-v2": "file:../../..",
"yargs": "^16.2.0"
}
}
62 changes: 62 additions & 0 deletions samples/node/x509_connect/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "removeComments": false, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"include": [
"*.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
41 changes: 41 additions & 0 deletions samples/util/cli_args.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,46 @@ function add_cognito_arguments(yargs) {
})
}

function add_x509_arguments(yargs) {
yargs
.option('x509_endpoint', {
description: 'The credentials endpoint to fetch x509 credentials from',
type: 'string',
default: '',
required: true
})
.option('x509_thing_name', {
description: 'Thing name to fetch x509 credentials on behalf of',
type: 'string',
default: '',
required: true
})
.option('x509_role_alias', {
description: 'Role alias to use with the x509 credentials provider',
type: 'string',
default: '',
required: true
})
.option('x509_cert', {
description: 'Path to the IoT thing certificate used in fetching x509 credentials',
type: 'string',
default: '',
required: true
})
.option('x509_key', {
description: 'Path to the IoT thing private key used in fetching x509 credentials',
type: 'string',
default: '',
required: true
})
.option('x509_ca_file', {
description: 'Path to the root certificate used in fetching x509 credentials',
type: 'string',
default: '',
required: false
})
}

/*
* Handles any non-specific arguments that are relevant to all samples
*/
Expand Down Expand Up @@ -401,6 +441,7 @@ exports.add_shadow_arguments = add_shadow_arguments;
exports.add_custom_authorizer_arguments = add_custom_authorizer_arguments;
exports.add_jobs_arguments = add_jobs_arguments;
exports.add_cognito_arguments = add_cognito_arguments;
exports.add_x509_arguments = add_x509_arguments
exports.apply_sample_arguments = apply_sample_arguments;
exports.build_connection_from_cli_args = build_connection_from_cli_args;
exports.build_mqtt5_client_from_cli_args = build_mqtt5_client_from_cli_args;

0 comments on commit 1bf923e

Please sign in to comment.