Golden image anatomy - AWS Prescriptive Guidance

Golden image anatomy

AWS IoT Greengrass core devices that are manufactured at scale are generally embedded Linux devices that have a Linux distribution that's constructed by using tools such as Yocto. Typically, the Greengrass edge runtime is baked into the distribution, as demonstrated by the Meta AWS project.

Such devices often have their file system organized into multiple partitions. This guide uses golden image as a catch-all term. Your device might have several golden images, to flash the various partitions.

Your golden image might encompass the entirety of the device's file system or just part of it. This guide focuses on the parts of the file system you need to consider for AWS IoT Greengrass, without prescribing how to assemble your images more broadly.

Greengrass directory tree

To understand the golden image methods discussed in this guide, review the structure of the Greengrass directory tree shown in the following table.

Directory

Description

alts

Launch parameters and symbolic links to the Greengrass nucleus version that is currently active.

bin

Binaries, if any are installed (for example, the Greengrass CLI binary if that component is installed). This directory is often empty.

cli_ipc_info

Scratchpad for Greengrass CLI interprocess communication (IPC). This directory is empty if you haven't installed the Greengrass CLI.

config

All Greengrass configuration, including component configuration.

deployments

Data for managing the state of deployments and rollbacks.

logs

Log files for the nucleus and other components.

packages

Artifacts and recipes for all components.

plugins

Storage for components of type aws.greengrass.plugin that you manually installed. Otherwise, this directory holds no data.

telemetry

Scratchpad used by Greengrass to aggregate telemetry data ready for publishing.

work

Scratchpad for components.

The logs, telemetry, and work directories contain only ephemeral data. They don't need be included in a golden image, so omit them if you want to minimize the size of the image.

The Greengrass CLI isn't usually installed on production devices, so the bin and cli_ipc_info directories are often empty and don't typically need to be included in a golden image.

The plugins directory includes data only if you manually installed a plugin (such as the fleet provisioning plugin or a custom provisioning plugin) when you installed Greengrass.

The data in the deployments directory is used only when a deployment is in progress and therefore isn't needed in a golden image.

Consequently the alts, config and packages directories are of the greatest interest. Sometimes these are the only Greengrass directories you need to include in a golden image, if you want to minimize the image size.

Contents of the packages directory

The packages directory has three subdirectories, as shown in the following table.

Subdirectory

Description

artifacts

The zipped component artifacts that Greengrass downloads during deployments.

artifacts-unarchived

For artifacts that are .zip archives, this directory contains the same artifacts, but they are unzipped so that components can use the artifact contents.

recipes

The component recipe files.

artifacts

The following example tree listing of packages/artifacts shows how artifacts are stored.

user@machine:~$ sudo tree /greengrass/v2/packages/artifacts /greengrass/v2/packages/artifacts ├── aws.greengrass.DockerApplicationManager ├── aws.greengrass.LogManager │ └── 2.3.7 │ └── aws.greengrass.LogManager.jar ├── aws.greengrass.Nucleus │ └── 2.12.6 │ └── aws.greengrass.nucleus.zip ├── aws.greengrass.SecretManager │ └── 2.1.8 │ └── aws.greengrass.SecretManager.jar ├── aws.greengrass.SecureTunneling │ └── 1.0.19 │ └── GreengrassV2SecureTunnelingComponent-1.0-all.jar ├── aws.greengrass.labs.CertificateRotator │ └── 1.1.0 │ └── certificate-rotator.zip ├── aws.greengrass.labs.HomeAssistant │ └── 1.0.0 │ └── home-assistant.zip └── aws.greengrass.telemetry.NucleusEmitter └── 1.0.8 └── aws.greengrass.telemetry.NucleusEmitter.jar 15 directories, 7 files

artifacts-unarchived

The following example tree listing of packages/artifacts-unarchived shows artifacts that are extracted from .zip files.

user@machine:~$ sudo tree /greengrass/v2/packages/artifacts-unarchived /greengrass/v2/packages/artifacts-unarchived ├── aws.greengrass.Nucleus │ └── 2.12.6 │ └── aws.greengrass.nucleus │ ├── LICENSE │ ├── NOTICE │ ├── README.md │ ├── THIRD-PARTY-LICENSES │ ├── bin │ │ ├── greengrass.exe │ │ ├── greengrass.service │ │ ├── greengrass.service.procd.template │ │ ├── greengrass.service.template │ │ ├── greengrass.xml.template │ │ ├── loader │ │ └── loader.cmd │ ├── conf │ │ └── recipe.yaml │ └── lib │ └── Greengrass.jar ├── aws.greengrass.SecureTunneling │ └── 1.0.19 ├── aws.greengrass.labs.CertificateRotator │ └── 1.1.0 │ └── certificate-rotator │ ├── __pycache__ │ │ ├── config.cpython-310.pyc │ │ ├── config.cpython-311.pyc │ │ ├── effective_config.cpython-310.pyc │ │ ├── effective_config.cpython-311.pyc │ │ ├── main.cpython-311.pyc │ │ ├── pki.cpython-310.pyc │ │ ├── pki.cpython-311.pyc │ │ ├── pki_file.cpython-310.pyc │ │ ├── pki_file.cpython-311.pyc │ │ ├── pki_hsm.cpython-310.pyc │ │ ├── pki_hsm.cpython-311.pyc │ │ ├── pubsub.cpython-310.pyc │ │ ├── pubsub.cpython-311.pyc │ │ ├── state.cpython-310.pyc │ │ ├── state.cpython-311.pyc │ │ ├── state_committing_certificate.cpython-310.pyc │ │ ├── state_committing_certificate.cpython-311.pyc │ │ ├── state_creating_certificate.cpython-310.pyc │ │ ├── state_creating_certificate.cpython-311.pyc │ │ ├── state_getting_job.cpython-310.pyc │ │ ├── state_getting_job.cpython-311.pyc │ │ ├── state_idle.cpython-310.pyc │ │ ├── state_idle.cpython-311.pyc │ │ ├── state_machine.cpython-310.pyc │ │ ├── state_machine.cpython-311.pyc │ │ ├── state_updating_job.cpython-310.pyc │ │ ├── state_updating_job.cpython-311.pyc │ │ ├── topic_base.cpython-310.pyc │ │ └── topic_base.cpython-311.pyc │ ├── config.py │ ├── effective_config.py │ ├── main.py │ ├── pki.py │ ├── pki_file.py │ ├── pki_hsm.py │ ├── pubsub.py │ ├── requirements.txt │ ├── scripts │ │ └── run.cmd │ ├── state.py │ ├── state_committing_certificate.py │ ├── state_creating_certificate.py │ ├── state_getting_job.py │ ├── state_idle.py │ ├── state_machine.py │ ├── state_updating_job.py │ └── topic_base.py └── aws.greengrass.labs.HomeAssistant └── 1.0.0 └── home-assistant ├── config │ ├── automations.yaml │ ├── configuration.yaml │ ├── groups.yaml │ ├── scenes.yaml │ └── scripts.yaml ├── docker-compose.yml ├── install.py └── secret.py 17 directories, 67 files

Note that the alts directory links to the Nucleus .jar file in packages/artifacts-unarchived. For example:

user@machine:~$ sudo ls -l /greengrass/v2/alts/init total 8 lrwxrwxrwx 1 root root 97 Jun 27 08:12 distro -> /greengrass/v2/packages/artifacts-unarchived/aws.greengrass.Nucleus/2.12.6/aws.greengrass.nucleus -rw-r--r-- 1 root root 16 Jun 27 07:07 launch.params

Therefore, packages/artifacts-unarchived must be included in your golden image.

recipes

The following example tree listing of packages/recipes shows how recipes are stored. As the listing indicates, recipes are stored with digests to help Greengrass determine whether it already has the correct files when it receives a deployment. This highly specific format makes it difficult to compose a golden image. Consequently, taking a snapshot of a golden device is the recommended method for creating a golden image.

user@machine:~$ sudo tree /greengrass/v2/packages/recipes /greengrass/v2/packages/recipes ├── 0ya1warrMfzlq5PUTvOgfHOununru_xCLUFACECM_R0@2.3.7.metadata.json ├── 0ya1warrMfzlq5PUTvOgfHOununru_xCLUFACECM_R0@2.3.7.recipe.yaml ├── 89r1-ak7xPauDt4O7EG03sSXVUO8ysdHTk-YdF0NAAc@2.12.6.metadata.json ├── 89r1-ak7xPauDt4O7EG03sSXVUO8ysdHTk-YdF0NAAc@2.12.6.recipe.yaml ├── VAZ-Grqe5g43yO7UtasQOR5jcQGILgPeRZQhVikLd9o@1.0.0.metadata.json ├── VAZ-Grqe5g43yO7UtasQOR5jcQGILgPeRZQhVikLd9o@1.0.0.recipe.yaml ├── ViMYPYs99-AzSt1gL2L2YD5P7sIN-yEhy23wWJK_JN8@1.0.8.metadata.json ├── ViMYPYs99-AzSt1gL2L2YD5P7sIN-yEhy23wWJK_JN8@1.0.8.recipe.yaml ├── _1hT2A6X0ZYtB_CfI_ZUOEMDV96DfQVkSmZh2bbGYXg@1.0.19.metadata.json ├── _1hT2A6X0ZYtB_CfI_ZUOEMDV96DfQVkSmZh2bbGYXg@1.0.19.recipe.yaml ├── gQWwM7MSL2kOsBADU9bOQJ1QqO8ZI3hqpbKT5Bv4Ijk@1.1.0.metadata.json ├── gQWwM7MSL2kOsBADU9bOQJ1QqO8ZI3hqpbKT5Bv4Ijk@1.1.0.recipe.yaml ├── j_j5Seyy01FOcIh95nBFy4HYf8P1kT-jW_nmV18ldbk@2.1.8.metadata.json └── j_j5Seyy01FOcIh95nBFy4HYf8P1kT-jW_nmV18ldbk@2.1.8.recipe.yaml 0 directories, 14 files

System service

If Greengrass is installed as a system service, as it commonly is for embedded Linux devices, the golden image must also include the directories that contain the systemd startup scripts.

Docker images

If your device uses AWS IoT Greengrass components that feature Docker images as artifacts, these artifacts sit outside the Greengrass directory tree. Therefore, you need to include the golden device's Docker image registry in your golden image. This registry is typically stored in /var/lib/docker.

Alternatively, you can use Docker commands to make a copy of the Docker images that are stored on your golden device, and then load those Docker images onto each device on your manufacturing line. In general, this method is slower and becomes less scalable as the number of Docker images increases.

Secrets

If your devices use the secret manager component to synchronize secrets from AWS Secrets Manager, these secrets are stored in the config/config.tlog file in the Greengrass directory tree of your golden device. For example:

{"TS":1718878001465, "TP":["services","aws.greengrass.SecretManager","runtime","secretResponse"], "W":"changed", "V":"{\"secrets\":[ { \"arn\":\"arn:aws:secretsmanager:us-east-1:111122223333:secret:greengrass-home-assistant-KIzJfZ\", \"name\":\"greengrass-home-assistant\", \"versionId\":\"8e481177-9250-4458-9f1f-3690d28e4ae9\", \"encryptedSecretString\":\"AgV4j+We ... A7QjdE1w==\", \"versionStages\":[\"AWSCURRENT\"], \"createdDate\":1660648425915 } ] } "}

These secrets are also stored in the corresponding config/effectiveConfig.yaml file:

aws.greengrass.SecretManager: componentType: "PLUGIN" configuration: cloudSecrets: - arn: "arn:aws:secretsmanager:us-east-1:111122223333:secret:greengrass-home-assistant-KIzJfZ" dependencies: - "aws.greengrass.Nucleus:SOFT" lifecycle: {} runtime: secretResponse: "{\"secrets\":[{\"arn\":\"arn:aws:secretsmanager:us-east-1:111122223333:secret:greengrass-home-assistant-KIzJfZ\"\ ,\"name\":\"greengrass-home-assistant\",\"versionId\":\"8e481177-9250-4458-9f1f-3690d28e4ae9\"\ ,\"encryptedSecretString\":\"AgV4Rpc9 ... MYeVALYQ==\"\ ,\"versionStages\":[\"AWSCURRENT\"],\"createdDate\":1660648425915}]}" version: "2.1.8"

Even if you include the config directory in your golden image, it's important to remember that the Greengrass secret manager component encrypts the secret with the golden device's private key. Because each device has a unique private key, keys that were encrypted by your golden device can't be decrypted by your production devices.

For this reason, we recommend that you remove encrypted secrets from the golden image to prevent your production devices from incorrectly decrypting golden device secrets. Your application components should function adequately, or at least fail gracefully, when secrets aren't present on disk, before a device has its first communication with the cloud.

When secrets are a hard requirement

If your organization requires your production devices to be populated with secrets during manufacture, your production line needs a script or program that replicates the behavior of the secret manager component, to populate secrets on each production device. We don't recommend this approach because of its complexity and the possibility that your secrets might briefly be held in cleartext on your production programming station.