mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-21 04:41:02 -05:00
clean-up
This commit is contained in:
parent
9fdba69ca2
commit
aafb9b7463
@ -1,412 +0,0 @@
|
|||||||
# Attestation
|
|
||||||
|
|
||||||
[comment]: hello
|
|
||||||
|
|
||||||
This page explains Constellation's attestation process and highlights the cornerstones of its trust model.
|
|
||||||
|
|
||||||
## Terms
|
|
||||||
|
|
||||||
The following lists terms and concepts that help to understand the attestation concept of Constellation.
|
|
||||||
|
|
||||||
### Trusted Platform Module (TPM)
|
|
||||||
|
|
||||||
A TPM chip is a dedicated tamper-resistant crypto-processor.
|
|
||||||
It can securely store artifacts such as passwords, certificates, encryption keys, or _runtime measurements_ (more on this below).
|
|
||||||
When a TPM is implemented in software, it's typically called a _virtual_ TPM (vTPM).
|
|
||||||
|
|
||||||
### Runtime measurement
|
|
||||||
|
|
||||||
A runtime measurement is a cryptographic hash of the memory pages of a so called _runtime component_. Runtime components of interest typically include a system's bootloader or OS kernel.
|
|
||||||
|
|
||||||
### Platform Configuration Register (PCR)
|
|
||||||
|
|
||||||
A Platform Configuration Register (PCR) is a memory location in the TPM that has some unique properties.
|
|
||||||
To store a new value in a PCR, the existing value is extended with a new value as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
PCR[N] = HASHalg( PCR[N] || ArgumentOfExtend )
|
|
||||||
```
|
|
||||||
|
|
||||||
The PCRs are typically used to store runtime measurements.
|
|
||||||
The new value of a PCR is always an extension of the existing value.
|
|
||||||
Thus, storing the measurements of multiple components into the same PCR irreversibly links them together.
|
|
||||||
|
|
||||||
### Measured boot
|
|
||||||
|
|
||||||
Measured boot builds on the concept of chained runtime measurements.
|
|
||||||
Each component in the boot chain loads and measures the next component into the PCR before executing it.
|
|
||||||
By comparing the resulting PCR values against trusted reference values, the integrity of the entire boot chain and thereby the running system can be ensured.
|
|
||||||
|
|
||||||
### Remote attestation (RA)
|
|
||||||
|
|
||||||
Remote attestation is the process of verifying certain properties of an application or platform, such as integrity and confidentiality, from a remote location.
|
|
||||||
In the case of a measured boot, the goal is to obtain a signed attestation statement on the PCR values of the boot measurements.
|
|
||||||
The statement can then be verified and compared to a set of trusted reference values.
|
|
||||||
This way, the integrity of the platform can be ensured before sharing secrets with it.
|
|
||||||
|
|
||||||
### Confidential virtual machine (CVM)
|
|
||||||
|
|
||||||
Confidential computing (CC) is the protection of data in-use with hardware-based trusted execution environments (TEEs).
|
|
||||||
With CVMs, TEEs encapsulate entire virtual machines and isolate them against the hypervisor, other VMs, and direct memory access.
|
|
||||||
After loading the initial VM image into encrypted memory, the hypervisor calls for a secure processor to measure these initial memory pages.
|
|
||||||
The secure processor locks these pages and generates an attestation report on the initial page measurements.
|
|
||||||
CVM memory pages are encrypted with a key that resides inside the secure processor, which makes sure only the guest VM can access them.
|
|
||||||
The attestation report is signed by the secure processor and can be verified using remote attestation via the certificate authority of the hardware vendor.
|
|
||||||
Such an attestation statement guarantees the confidentiality and integrity of a CVM.
|
|
||||||
|
|
||||||
### Attested TLS (aTLS)
|
|
||||||
|
|
||||||
In a CC environment, attested TLS (aTLS) can be used to establish secure connections between two parties using the remote attestation features of the CC components.
|
|
||||||
|
|
||||||
aTLS modifies the TLS handshake by embedding an attestation statement into the TLS certificate.
|
|
||||||
Instead of relying on a certificate authority, aTLS uses this attestation statement to establish trust in the certificate.
|
|
||||||
|
|
||||||
The protocol can be used by clients to verify a server certificate, by a server to verify a client certificate, or for mutual verification (mutual aTLS).
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The challenge for Constellation is to lift a CVM's attestation statement to the Kubernetes software layer and make it end-to-end verifiable.
|
|
||||||
From there, Constellation needs to expand the attestation from a single CVM to the entire cluster.
|
|
||||||
|
|
||||||
The [_JoinService_](microservices.md#joinservice) and [_VerificationService_](microservices.md#verificationservice) are where all runs together.
|
|
||||||
Internally, the _JoinService_ uses remote attestation to securely join CVM nodes to the cluster.
|
|
||||||
Externally, the _VerificationService_ provides an attestation statement for the cluster's CVMs and configuration.
|
|
||||||
|
|
||||||
The following explains the details of both steps.
|
|
||||||
|
|
||||||
## Node attestation
|
|
||||||
|
|
||||||
The idea is that Constellation nodes should have verifiable integrity from the CVM hardware measurement up to the Kubernetes software layer.
|
|
||||||
The solution is a verifiable boot chain and an integrity-protected runtime environment.
|
|
||||||
|
|
||||||
Constellation uses measured boot within CVMs, measuring each component in the boot process before executing it.
|
|
||||||
Outside of CC, this is usually implemented via TPMs.
|
|
||||||
CVM technologies differ in how they implement runtime measurements, but the general concepts are similar to those of a TPM.
|
|
||||||
For simplicity, TPM terminology like _PCR_ is used in the following.
|
|
||||||
|
|
||||||
When a Constellation node image boots inside a CVM, measured boot is used for all stages and components of the boot chain.
|
|
||||||
This process goes up to the root filesystem.
|
|
||||||
The root filesystem is mounted read-only with integrity protection.
|
|
||||||
For the details on the image and boot stages see the [image architecture](images.md) documentation.
|
|
||||||
Any changes to the image will inevitably also change the corresponding PCR values.
|
|
||||||
To create a node attestation statement, the Constellation image obtains a CVM attestation statement from the hardware.
|
|
||||||
This includes the runtime measurements and thereby binds the measured boot results to the CVM hardware measurement.
|
|
||||||
|
|
||||||
In addition to the image measurements, Constellation extends a PCR during the [initialization phase](../../workflows/create.md) that irrevocably marks the node as initialized.
|
|
||||||
The measurement is created using the [_clusterID_](keys.md#cluster-identity), tying all future attestation statements to this ID.
|
|
||||||
Thereby, an attestation statement is unique for every cluster and a node can be identified unambiguously as being initialized.
|
|
||||||
|
|
||||||
To verify an attestation, the hardware's signature and a statement are verified first to establish trust in the contained runtime measurements.
|
|
||||||
If successful, the measurements are verified against the trusted values of the particular Constellation release version.
|
|
||||||
Finally, the measurement of the _clusterID_ can be compared by calculating it with the [master secret](keys.md#master-secret).
|
|
||||||
|
|
||||||
### Runtime measurements
|
|
||||||
|
|
||||||
Constellation uses runtime measurements to implement the measured boot approach.
|
|
||||||
As stated above, the underlying hardware technology and guest firmware differ in their implementations of runtime measurements.
|
|
||||||
The following gives a detailed description of the available measurements in the different cloud environments.
|
|
||||||
|
|
||||||
The runtime measurements consist of two types of values:
|
|
||||||
|
|
||||||
- **Measurements produced by the cloud infrastructure and firmware of the CVM**:
|
|
||||||
These are measurements of closed-source firmware and other values controlled by the cloud provider.
|
|
||||||
While not being reproducible for the user, some of them can be compared against previously observed values.
|
|
||||||
Others may change frequently and aren't suitable for verification.
|
|
||||||
The [signed image measurements](#chain-of-trust) include measurements that are known, previously observed values.
|
|
||||||
|
|
||||||
- **Measurements produced by the Constellation bootloader and boot chain**:
|
|
||||||
The Constellation Bootloader takes over from the CVM firmware and [measures the rest of the boot chain](images.md).
|
|
||||||
The Constellation [Bootstrapper](microservices.md#bootstrapper) is the first user mode component that runs in a Constellation image.
|
|
||||||
It extends PCR registers with the [IDs](keys.md#cluster-identity) of the cluster marking a node as initialized.
|
|
||||||
|
|
||||||
Constellation allows to specify in the config which measurements should be enforced during the attestation process.
|
|
||||||
Enforcing non-reproducible measurements controlled by the cloud provider means that changes in these values require manual updates to the cluster's config.
|
|
||||||
By default, Constellation only enforces measurements that are stable values produced by the infrastructure or by Constellation directly.
|
|
||||||
|
|
||||||
<Tabs groupId="csp">
|
|
||||||
<TabItem value="aws" label="AWS">
|
|
||||||
|
|
||||||
Constellation uses the [vTPM](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nitrotpm.html) (NitroTPM) feature of the [AWS Nitro System](http://aws.amazon.com/ec2/nitro/) on AWS for runtime measurements.
|
|
||||||
|
|
||||||
The vTPM adheres to the [TPM 2.0](https://trustedcomputinggroup.org/resource/tpm-library-specification/) specification.
|
|
||||||
The VMs are attested by obtaining signed PCR values over the VM's boot configuration from the TPM and comparing them to a known, good state (measured boot).
|
|
||||||
|
|
||||||
The following table lists all PCR values of the vTPM and the measured components.
|
|
||||||
It also lists what components of the boot chain did the measurements and if the value is reproducible and verifiable.
|
|
||||||
The latter means that the value can be generated offline and compared to the one in the vTPM.
|
|
||||||
|
|
||||||
| PCR | Components | Measured by | Reproducible and verifiable |
|
|
||||||
| ----------- | ---------------------------------------------------------------- | -------------------------------------- | --------------------------- |
|
|
||||||
| 0 | Firmware | AWS | No |
|
|
||||||
| 1 | Firmware | AWS | No |
|
|
||||||
| 2 | Firmware | AWS | No |
|
|
||||||
| 3 | Firmware | AWS | No |
|
|
||||||
| 4 | Constellation Bootloader, Kernel, initramfs, Kernel command line | AWS, Constellation Bootloader | Yes |
|
|
||||||
| 5 | Firmware | AWS | No |
|
|
||||||
| 6 | Firmware | AWS | No |
|
|
||||||
| 7 | Secure Boot Policy | AWS, Constellation Bootloader | No |
|
|
||||||
| 8 | - | - | - |
|
|
||||||
| 9 | initramfs, Kernel command line | Linux Kernel | Yes |
|
|
||||||
| 10 | User space | Linux IMA | No[^1] |
|
|
||||||
| 11 | Unified Kernel Image components | Constellation Bootloader | Yes |
|
|
||||||
| 12 | Reserved | (User space, Constellation Bootloader) | Yes |
|
|
||||||
| 13 | Reserved | (Constellation Bootloader) | Yes |
|
|
||||||
| 14 | Secure Boot State | Constellation Bootloader | No |
|
|
||||||
| 15 | ClusterID | Constellation Bootstrapper | Yes |
|
|
||||||
| 16–23 | Unused | - | - |
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="azure" label="Azure">
|
|
||||||
|
|
||||||
Constellation uses the [vTPM](https://docs.microsoft.com/en-us/azure/virtual-machines/trusted-launch#vtpm) feature of Azure CVMs for runtime measurements.
|
|
||||||
This vTPM adheres to the [TPM 2.0](https://trustedcomputinggroup.org/resource/tpm-library-specification/) specification.
|
|
||||||
It provides a [measured boot](https://docs.microsoft.com/en-us/azure/security/fundamentals/measured-boot-host-attestation#measured-boot) verification that's based on the trusted launch feature of [Trusted Launch VMs](https://docs.microsoft.com/en-us/azure/virtual-machines/trusted-launch).
|
|
||||||
|
|
||||||
The following table lists all PCR values of the vTPM and the measured components.
|
|
||||||
It also lists what components of the boot chain did the measurements and if the value is reproducible and verifiable.
|
|
||||||
The latter means that the value can be generated offline and compared to the one in the vTPM.
|
|
||||||
|
|
||||||
| PCR | Components | Measured by | Reproducible and verifiable |
|
|
||||||
| ----------- | ---------------------------------------------------------------- | -------------------------------------- | --------------------------- |
|
|
||||||
| 0 | Firmware | Azure | No |
|
|
||||||
| 1 | Firmware | Azure | No |
|
|
||||||
| 2 | Firmware | Azure | No |
|
|
||||||
| 3 | Firmware | Azure | No |
|
|
||||||
| 4 | Constellation Bootloader, Kernel, initramfs, Kernel command line | Azure, Constellation Bootloader | Yes |
|
|
||||||
| 5 | Reserved | Azure | No |
|
|
||||||
| 6 | VM Unique ID | Azure | No |
|
|
||||||
| 7 | Secure Boot State | Azure, Constellation Bootloader | No |
|
|
||||||
| 8 | - | - | - |
|
|
||||||
| 9 | initramfs, Kernel command line | Linux Kernel | Yes |
|
|
||||||
| 10 | User space | Linux IMA | No[^1] |
|
|
||||||
| 11 | Unified Kernel Image components | Constellation Bootloader | Yes |
|
|
||||||
| 12 | Reserved | (User space, Constellation Bootloader) | Yes |
|
|
||||||
| 13 | Reserved | (Constellation Bootloader) | Yes |
|
|
||||||
| 14 | Secure Boot State | Constellation Bootloader | No |
|
|
||||||
| 15 | ClusterID | Constellation Bootstrapper | Yes |
|
|
||||||
| 16–23 | Unused | - | - |
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="gcp" label="GCP">
|
|
||||||
|
|
||||||
Constellation uses the [vTPM](https://cloud.google.com/compute/confidential-vm/docs/about-cvm) feature of CVMs on GCP for runtime measurements.
|
|
||||||
Note that this vTPM doesn't run inside the hardware-protected CVM context, but is emulated by the hypervisor.
|
|
||||||
|
|
||||||
The vTPM adheres to the [TPM 2.0](https://trustedcomputinggroup.org/resource/tpm-library-specification/) specification.
|
|
||||||
It provides a [launch attestation report](https://cloud.google.com/compute/confidential-vm/docs/monitoring#about_launch_attestation_report_events) that's based on the measured boot feature of [Shielded VMs](https://cloud.google.com/compute/shielded-vm/docs/shielded-vm#measured-boot).
|
|
||||||
|
|
||||||
The following table lists all PCR values of the vTPM and the measured components.
|
|
||||||
It also lists what components of the boot chain did the measurements and if the value is reproducible and verifiable.
|
|
||||||
The latter means that the value can be generated offline and compared to the one in the vTPM.
|
|
||||||
|
|
||||||
| PCR | Components | Measured by | Reproducible and verifiable |
|
|
||||||
| ----------- | ---------------------------------------------------------------- | -------------------------------------- | --------------------------- |
|
|
||||||
| 0 | CVM version and technology | GCP | No |
|
|
||||||
| 1 | Firmware | GCP | No |
|
|
||||||
| 2 | Firmware | GCP | No |
|
|
||||||
| 3 | Firmware | GCP | No |
|
|
||||||
| 4 | Constellation Bootloader, Kernel, initramfs, Kernel command line | GCP, Constellation Bootloader | Yes |
|
|
||||||
| 5 | Disk GUID partition table | GCP | No |
|
|
||||||
| 6 | Disk GUID partition table | GCP | No |
|
|
||||||
| 7 | GCP Secure Boot Policy | GCP, Constellation Bootloader | No |
|
|
||||||
| 8 | - | - | - |
|
|
||||||
| 9 | initramfs, Kernel command line | Linux Kernel | Yes |
|
|
||||||
| 10 | User space | Linux IMA | No[^1] |
|
|
||||||
| 11 | Unified Kernel Image components | Constellation Bootloader | Yes |
|
|
||||||
| 12 | Reserved | (User space, Constellation Bootloader) | Yes |
|
|
||||||
| 13 | Reserved | (Constellation Bootloader) | Yes |
|
|
||||||
| 14 | Secure Boot State | Constellation Bootloader | No |
|
|
||||||
| 15 | ClusterID | Constellation Bootstrapper | Yes |
|
|
||||||
| 16–23 | Unused | - | - |
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="stackit" label="STACKIT">
|
|
||||||
|
|
||||||
Constellation uses a hypervisor-based vTPM for runtime measurements.
|
|
||||||
|
|
||||||
The vTPM adheres to the [TPM 2.0](https://trustedcomputinggroup.org/resource/tpm-library-specification/) specification.
|
|
||||||
The VMs are attested by obtaining signed PCR values over the VM's boot configuration from the TPM and comparing them to a known, good state (measured boot).
|
|
||||||
|
|
||||||
The following table lists all PCR values of the vTPM and the measured components.
|
|
||||||
It also lists what components of the boot chain did the measurements and if the value is reproducible and verifiable.
|
|
||||||
The latter means that the value can be generated offline and compared to the one in the vTPM.
|
|
||||||
|
|
||||||
| PCR | Components | Measured by | Reproducible and verifiable |
|
|
||||||
| ----------- | ---------------------------------------------------------------- | -------------------------------------- | --------------------------- |
|
|
||||||
| 0 | Firmware | STACKIT | No |
|
|
||||||
| 1 | Firmware | STACKIT | No |
|
|
||||||
| 2 | Firmware | STACKIT | No |
|
|
||||||
| 3 | Firmware | STACKIT | No |
|
|
||||||
| 4 | Constellation Bootloader, Kernel, initramfs, Kernel command line | STACKIT, Constellation Bootloader | Yes |
|
|
||||||
| 5 | Firmware | STACKIT | No |
|
|
||||||
| 6 | Firmware | STACKIT | No |
|
|
||||||
| 7 | Secure Boot Policy | STACKIT, Constellation Bootloader | No |
|
|
||||||
| 8 | - | - | - |
|
|
||||||
| 9 | initramfs, Kernel command line | Linux Kernel | Yes |
|
|
||||||
| 10 | User space | Linux IMA | No[^1] |
|
|
||||||
| 11 | Unified Kernel Image components | Constellation Bootloader | Yes |
|
|
||||||
| 12 | Reserved | (User space, Constellation Bootloader) | Yes |
|
|
||||||
| 13 | Reserved | (Constellation Bootloader) | Yes |
|
|
||||||
| 14 | Secure Boot State | Constellation Bootloader | No |
|
|
||||||
| 15 | ClusterID | Constellation Bootstrapper | Yes |
|
|
||||||
| 16–23 | Unused | - | - |
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
### CVM verification
|
|
||||||
|
|
||||||
To verify the integrity of the received attestation statement, a chain of trust from the CVM technology to the interface providing the statement has to be established.
|
|
||||||
For verification of the CVM technology, Constellation may expose additional options in its config file.
|
|
||||||
|
|
||||||
<Tabs groupId="csp">
|
|
||||||
<TabItem value="aws" label="AWS">
|
|
||||||
|
|
||||||
On AWS, AMD SEV-SNP is used to provide runtime encryption to the VMs.
|
|
||||||
An SEV-SNP attestation report is used to establish trust in the VM.
|
|
||||||
You may customize certain parameters for verification of the attestation statement using the Constellation config file.
|
|
||||||
|
|
||||||
- TCB versions
|
|
||||||
|
|
||||||
You can set the minimum version numbers of components in the SEV-SNP TCB.
|
|
||||||
Use the latest versions to enforce that only machines with the most recent firmware updates are allowed to join the cluster.
|
|
||||||
Alternatively, you can set a lower minimum version to allow slightly out-of-date machines to still be able to join the cluster.
|
|
||||||
|
|
||||||
- AMD Root Key Certificate
|
|
||||||
|
|
||||||
This certificate is the root of trust for verifying the SEV-SNP certificate chain.
|
|
||||||
|
|
||||||
- AMD Signing Key Certificate
|
|
||||||
|
|
||||||
This is the intermediate certificate for verifying the SEV-SNP report's signature.
|
|
||||||
If it's not specified, the CLI fetches it from the AMD key distribution server.
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="azure" label="Azure SEV-SNP">
|
|
||||||
|
|
||||||
On Azure, AMD SEV-SNP is used to provide runtime encryption to the VMs.
|
|
||||||
An SEV-SNP attestation report is used to establish trust in the vTPM running inside the VM.
|
|
||||||
You may customize certain parameters for verification of the attestation statement using the Constellation config file.
|
|
||||||
|
|
||||||
- TCB versions
|
|
||||||
|
|
||||||
You can set the minimum version numbers of components in the SEV-SNP TCB.
|
|
||||||
Use the latest versions to enforce that only machines with the most recent firmware updates are allowed to join the cluster.
|
|
||||||
Alternatively, you can set a lower minimum version to allow slightly out-of-date machines to still be able to join the cluster.
|
|
||||||
|
|
||||||
- AMD Root Key Certificate
|
|
||||||
|
|
||||||
This certificate is the root of trust for verifying the SEV-SNP certificate chain.
|
|
||||||
|
|
||||||
- Firmware Signer
|
|
||||||
|
|
||||||
This config option allows you to specify how the firmware signer should be verified.
|
|
||||||
More explicitly, it controls the verification of the `IDKeyDigest` value in the SEV-SNP attestation report.
|
|
||||||
You can provide a list of accepted key digests and specify a policy on how this list is compared against the reported `IDKeyDigest`.
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="gcp" label="GCP">
|
|
||||||
|
|
||||||
On GCP, AMD SEV-SNP is used to provide runtime encryption to the VMs.
|
|
||||||
An SEV-SNP attestation report is used to establish trust in the VM.
|
|
||||||
You may customize certain parameters for verification of the attestation statement using the Constellation config file.
|
|
||||||
|
|
||||||
- TCB versions
|
|
||||||
|
|
||||||
You can set the minimum version numbers of components in the SEV-SNP TCB.
|
|
||||||
Use the latest versions to enforce that only machines with the most recent firmware updates are allowed to join the cluster.
|
|
||||||
Alternatively, you can set a lower minimum version to allow slightly out-of-date machines to still be able to join the cluster.
|
|
||||||
|
|
||||||
- AMD Root Key Certificate
|
|
||||||
|
|
||||||
This certificate is the root of trust for verifying the SEV-SNP certificate chain.
|
|
||||||
|
|
||||||
- AMD Signing Key Certificate
|
|
||||||
|
|
||||||
This is the intermediate certificate for verifying the SEV-SNP report's signature.
|
|
||||||
If it's not specified, the CLI fetches it from the AMD key distribution server.
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="stackit" label="STACKIT">
|
|
||||||
|
|
||||||
On STACKIT, AMD SEV-ES is used to provide runtime encryption to the VMs.
|
|
||||||
The hypervisor-based vTPM is used to establish trust in the VM via [runtime measurements](#runtime-measurements).
|
|
||||||
There is no additional configuration available for STACKIT.
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Cluster attestation
|
|
||||||
|
|
||||||
Cluster-facing, Constellation's [_JoinService_](microservices.md#joinservice) verifies each node joining the cluster given the configured ground truth runtime measurements.
|
|
||||||
User-facing, the [_VerificationService_](microservices.md#verificationservice) provides an interface to verify a node using remote attestation.
|
|
||||||
By verifying the first node during the [initialization](microservices.md#bootstrapper) and configuring the ground truth measurements that are subsequently enforced by the _JoinService_, the whole cluster is verified in a transitive way.
|
|
||||||
|
|
||||||
### Cluster-facing attestation
|
|
||||||
|
|
||||||
The _JoinService_ is provided with the runtime measurements of the whitelisted Constellation image version as the ground truth.
|
|
||||||
During the initialization and the cluster bootstrapping, each node connects to the _JoinService_ using [aTLS](#attested-tls-atls).
|
|
||||||
During the handshake, the node transmits an attestation statement including its runtime measurements.
|
|
||||||
The _JoinService_ verifies that statement and compares the measurements against the ground truth.
|
|
||||||
For details of the initialization process check the [microservice descriptions](microservices.md).
|
|
||||||
|
|
||||||
After the initialization, every node updates its runtime measurements with the _clusterID_ value, marking it irreversibly as initialized.
|
|
||||||
When an initialized node tries to join another cluster, its measurements inevitably mismatch the measurements of an uninitialized node and it will be declined.
|
|
||||||
|
|
||||||
### User-facing attestation
|
|
||||||
|
|
||||||
The [_VerificationService_](microservices.md#verificationservice) provides an endpoint for obtaining its hardware-based remote attestation statement, which includes the runtime measurements.
|
|
||||||
A user can [verify](../../workflows/verify-cluster.md) this statement and compare the measurements against the configured ground truth and, thus, verify the identity and integrity of all Constellation components and the cluster configuration. Subsequently, the user knows that the entire cluster is in the expected state and is trustworthy.
|
|
||||||
|
|
||||||
## Putting it all together
|
|
||||||
|
|
||||||
This section puts the aforementioned concepts together and illustrate how trust into a Constellation cluster is established and maintained.
|
|
||||||
|
|
||||||
### CLI and node images
|
|
||||||
|
|
||||||
It all starts with the CLI executable. The CLI is signed by Edgeless Systems. To ensure non-repudiability for CLI releases, Edgeless Systems publishes corresponding signatures to the public ledger of the [sigstore project](https://www.sigstore.dev/). There's a [step-by-step guide](../../workflows/verify-cli.md) on how to verify CLI signatures based on sigstore.
|
|
||||||
|
|
||||||
The CLI contains the latest runtime measurements of the Constellation node image for all supported cloud platforms. In case a different version of the node image is to be used, the corresponding runtime measurements can be fetched using the CLI's [fetch-measurements command](../../reference/cli.md#constellation-config-fetch-measurements). This command downloads the runtime measurements and the corresponding signature from cdn.confidential.cloud. See for example the following files corresponding to node image v2.16.3:
|
|
||||||
|
|
||||||
- [Measurements](https://cdn.confidential.cloud/constellation/v2/ref/-/stream/stable/v2.16.3/image/measurements.json)
|
|
||||||
- [Signature](https://cdn.confidential.cloud/constellation/v2/ref/-/stream/stable/v2.16.3/image/measurements.json.sig)
|
|
||||||
|
|
||||||
The CLI contains the long-term public key of Edgeless Systems to verify the signature of downloaded runtime measurements.
|
|
||||||
|
|
||||||
### Cluster creation
|
|
||||||
|
|
||||||
When a cluster is [created](../../workflows/create.md), the CLI automatically verifies the runtime measurements of the _first node_ using remote attestation. Based on this, the CLI and the first node set up a temporary TLS connection. This [aTLS](#attested-tls-atls) connection is used for two things:
|
|
||||||
|
|
||||||
1. The CLI sends the [master secret](keys.md#master-secret) of the to-be-created cluster to the CLI. The master secret is generated by the first node.
|
|
||||||
2. The first node sends a [kubeconfig file](https://www.redhat.com/sysadmin/kubeconfig) with Kubernetes credentials to the CLI.
|
|
||||||
|
|
||||||
After this, the aTLS connection is closed and the first node bootstraps the Kubernetes cluster. All subsequent interactions between the CLI and the cluster go via the [Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/) server running inside the cluster. The CLI (and other tools like kubectl) use the credentials referenced by the kubeconfig file to authenticate themselves towards the Kubernetes API server and to establish a mTLS connection.
|
|
||||||
|
|
||||||
The CLI connects to the Kubernetes API to write the runtime measurements for the applicable node image to etcd. The JoinService uses these runtime measurements to verify all nodes that join the cluster subsequently.
|
|
||||||
|
|
||||||
### Chain of trust
|
|
||||||
|
|
||||||
In summary, there's a chain of trust based on cryptographic signatures that goes from the user to the cluster via the CLI. This is illustrated in the following diagram.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A[User]-- "verifies" -->B[CLI]
|
|
||||||
B[CLI]-- "verifies" -->C([Runtime measurements])
|
|
||||||
D[Edgeless Systems]-- "signs" -->B[CLI]
|
|
||||||
D[Edgeless Systems]-- "signs" -->C([Runtime measurements])
|
|
||||||
B[CLI]-- "verifies (remote attestation)" -->E[First node]
|
|
||||||
E[First node]-- "verifies (remote attestation)" -->F[Other nodes]
|
|
||||||
C([Runtime measurements]) -.-> E[First node]
|
|
||||||
C([Runtime measurements]) -.-> F[Other nodes]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Upgrades
|
|
||||||
|
|
||||||
Whenever a cluster is [upgraded](../../workflows/upgrade.md) to a new version of the node image, the CLI sends the corresponding runtime measurements via the Kubernetes API server. The new runtime measurements are stored in etcd within the cluster and replace any previous runtime measurements. The new runtime measurements are then used automatically by the JoinService for the verification of new nodes.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
[^1]:
|
|
||||||
Linux IMA produces runtime measurements of user-space binaries.
|
|
||||||
However, these measurements aren't deterministic and thus, PCR\[10] can't be compared to a constant value.
|
|
||||||
Instead, a policy engine must be used to verify the TPM event log against a policy.
|
|
@ -1,62 +0,0 @@
|
|||||||
# Encrypted persistent storage
|
|
||||||
|
|
||||||
Confidential VMs provide runtime memory encryption to protect data in use.
|
|
||||||
In the context of Kubernetes, this is sufficient for the confidentiality and integrity of stateless services.
|
|
||||||
Consider a front-end web server, for example, that keeps all connection information cached in main memory.
|
|
||||||
No sensitive data is ever written to an insecure medium.
|
|
||||||
However, many real-world applications need some form of state or data-lake service that's connected to a persistent storage device and requires encryption at rest.
|
|
||||||
As described in [Use persistent storage](../../workflows/storage.md), cloud service providers (CSPs) use the container storage interface (CSI) to make their storage solutions available to Kubernetes workloads.
|
|
||||||
These CSI storage solutions often support some sort of encryption.
|
|
||||||
For example, Google Cloud [encrypts data at rest by default](https://cloud.google.com/security/encryption/default-encryption), without any action required by the customer.
|
|
||||||
|
|
||||||
## Cloud provider-managed encryption
|
|
||||||
|
|
||||||
CSP-managed storage solutions encrypt the data in the cloud backend before writing it physically to disk.
|
|
||||||
In the context of confidential computing and Constellation, the CSP and its managed services aren't trusted.
|
|
||||||
Hence, cloud provider-managed encryption protects your data from offline hardware access to physical storage devices.
|
|
||||||
It doesn't protect it from anyone with infrastructure-level access to the storage backend or a malicious insider in the cloud platform.
|
|
||||||
Even with "bring your own key" or similar concepts, the CSP performs the encryption process with access to the keys and plaintext data.
|
|
||||||
|
|
||||||
In the security model of Constellation, securing persistent storage and thereby data at rest requires that all cryptographic operations are performed inside a trusted execution environment.
|
|
||||||
Consequently, using CSP-managed encryption of persistent storage usually isn't an option.
|
|
||||||
|
|
||||||
## Constellation-managed encryption
|
|
||||||
|
|
||||||
Constellation provides CSI drivers for storage solutions in all major clouds with built-in encryption support.
|
|
||||||
Block storage provisioned by the CSP is [mapped](https://guix.gnu.org/manual/en/html_node/Mapped-Devices.html) using the [dm-crypt](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-crypt.html), and optionally the [dm-integrity](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-integrity.html), kernel modules, before it's formatted and accessed by the Kubernetes workloads.
|
|
||||||
All cryptographic operations happen inside the trusted environment of the confidential Constellation node.
|
|
||||||
|
|
||||||
Note that for integrity-protected disks, [volume expansion](https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/) isn't supported.
|
|
||||||
|
|
||||||
By default the driver uses data encryption keys (DEKs) issued by the Constellation [_KeyService_](microservices.md#keyservice).
|
|
||||||
The DEKs are in turn derived from the Constellation's key encryption key (KEK), which is directly derived from the [master secret](keys.md#master-secret).
|
|
||||||
This is the recommended mode of operation, and also requires the least amount of setup by the cluster administrator.
|
|
||||||
|
|
||||||
Alternatively, the driver can be configured to use a key management system to store and access KEKs and DEKs.
|
|
||||||
|
|
||||||
Refer to [keys and cryptography](keys.md) for more details on key management in Constellation.
|
|
||||||
|
|
||||||
Once deployed and configured, the CSI driver ensures transparent encryption and integrity of all persistent volumes provisioned via its storage class.
|
|
||||||
Data at rest is secured without any additional actions required by the developer.
|
|
||||||
|
|
||||||
## Cryptographic algorithms
|
|
||||||
|
|
||||||
This section gives an overview of the libraries, cryptographic algorithms, and their configurations, used in Constellation's CSI drivers.
|
|
||||||
|
|
||||||
### dm-crypt
|
|
||||||
|
|
||||||
To interact with the dm-crypt kernel module, Constellation uses [libcryptsetup](https://gitlab.com/cryptsetup/cryptsetup/).
|
|
||||||
New devices are formatted as [LUKS2](https://gitlab.com/cryptsetup/LUKS2-docs/-/tree/master) partitions with a sector size of 4096 bytes.
|
|
||||||
The used key derivation function is [Argon2id](https://datatracker.ietf.org/doc/html/rfc9106) with the [recommended parameters for memory-constrained environments](https://datatracker.ietf.org/doc/html/rfc9106#section-7.4) of 3 iterations and 64 MiB of memory, utilizing 4 parallel threads.
|
|
||||||
For encryption Constellation uses AES in XTS-Plain64. The key size is 512 bit.
|
|
||||||
|
|
||||||
### dm-integrity
|
|
||||||
|
|
||||||
To interact with the dm-integrity kernel module, Constellation uses [libcryptsetup](https://gitlab.com/cryptsetup/cryptsetup/).
|
|
||||||
When enabled, the used data integrity algorithm is [HMAC](https://datatracker.ietf.org/doc/html/rfc2104) with SHA256 as the hash function.
|
|
||||||
The tag size is 32 Bytes.
|
|
||||||
|
|
||||||
## Encrypted S3 object storage
|
|
||||||
|
|
||||||
Constellation comes with a service that you can use to transparently retrofit client-side encryption to existing applications that use S3 (AWS or compatible) for storage.
|
|
||||||
To learn more, check out the [s3proxy documentation](../../workflows/s3proxy.md).
|
|
@ -1,49 +0,0 @@
|
|||||||
# Constellation images
|
|
||||||
|
|
||||||
Constellation uses a minimal version of Fedora as the operating system running inside confidential VMs. This Linux distribution is optimized for containers and designed to be stateless.
|
|
||||||
The Constellation images provide measured boot and an immutable filesystem.
|
|
||||||
|
|
||||||
## Measured boot
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
Firmware --> Bootloader
|
|
||||||
Bootloader --> uki
|
|
||||||
subgraph uki[Unified Kernel Image]
|
|
||||||
Kernel[Kernel]
|
|
||||||
initramfs[Initramfs]
|
|
||||||
cmdline[Kernel Command Line]
|
|
||||||
end
|
|
||||||
uki --> rootfs[Root Filesystem]
|
|
||||||
```
|
|
||||||
|
|
||||||
Measured boot uses a Trusted Platform Module (TPM) to measure every part of the boot process. This allows for verification of the integrity of a running system at any point in time. To ensure correct measurements of every stage, each stage is responsible to measure the next stage before transitioning.
|
|
||||||
|
|
||||||
### Firmware
|
|
||||||
|
|
||||||
With confidential VMs, the firmware is the root of trust and is measured automatically at boot. After initialization, the firmware will load and measure the bootloader before executing it.
|
|
||||||
|
|
||||||
### Bootloader
|
|
||||||
|
|
||||||
The bootloader is the first modifiable part of the boot chain. The bootloader is tasked with loading the kernel, initramfs and setting the kernel command line. The Constellation bootloader measures these components before starting the kernel.
|
|
||||||
|
|
||||||
### initramfs
|
|
||||||
|
|
||||||
The initramfs is a small filesystem loaded to prepare the actual root filesystem. The Constellation initramfs maps the block device containing the root filesystem with [dm-verity](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html). The initramfs then mounts the root filesystem from the mapped block device.
|
|
||||||
|
|
||||||
dm-verity provides integrity checking using a cryptographic hash tree. When a block is read, its integrity is checked by verifying the tree against a trusted root hash. The initramfs reads this root hash from the previously measured kernel command line. Thus, if any block of the root filesystem's device is modified on disk, trying to read the modified block will result in a kernel panic at runtime.
|
|
||||||
|
|
||||||
After mounting the root filesystem, the initramfs will switch over and start the `init` process of the integrity-protected root filesystem.
|
|
||||||
|
|
||||||
## State disk
|
|
||||||
|
|
||||||
In addition to the read-only root filesystem, each Constellation node has a disk for storing state data.
|
|
||||||
This disk is mounted readable and writable by the initramfs and contains data that should persist across reboots.
|
|
||||||
Such data can contain sensitive information and, therefore, must be stored securely.
|
|
||||||
To that end, the state disk is protected by authenticated encryption.
|
|
||||||
See the section on [keys and encryption](keys.md#storage-encryption) for more information on the cryptographic primitives in use.
|
|
||||||
|
|
||||||
## Kubernetes components
|
|
||||||
|
|
||||||
During initialization, the [*Bootstrapper*](microservices.md#bootstrapper) downloads and verifies the [Kubernetes components](https://kubernetes.io/docs/concepts/overview/components/) as configured by the user.
|
|
||||||
They're stored on the state partition and can be updated once new releases need to be installed.
|
|
@ -1,30 +0,0 @@
|
|||||||
# Key concepts
|
|
||||||
|
|
||||||
Constellation is a cloud-based confidential orchestration platform.
|
|
||||||
The foundation of Constellation is Kubernetes and therefore shares the same technology stack and architecture principles.
|
|
||||||
To learn more about Constellation and Kubernetes, see [product overview](../../overview/product.md).
|
|
||||||
|
|
||||||
## Orchestration and updates
|
|
||||||
|
|
||||||
As a cluster administrator, you can use the [Constellation CLI](orchestration.md) to install and deploy a cluster.
|
|
||||||
Updates are provided in accordance with the [support policy](versions.md).
|
|
||||||
|
|
||||||
## Microservices and attestation
|
|
||||||
|
|
||||||
Constellation manages the nodes and network in your cluster. All nodes are bootstrapped by the [_Bootstrapper_](microservices.md#bootstrapper). They're verified and authenticated by the [_JoinService_](microservices.md#joinservice) before being added to the cluster and the network. Finally, the entire cluster can be verified via the [_VerificationService_](microservices.md#verificationservice) using [remote attestation](attestation.md).
|
|
||||||
|
|
||||||
## Node images and verified boot
|
|
||||||
|
|
||||||
Constellation comes with operating system images for Kubernetes control-plane and worker nodes.
|
|
||||||
They're highly optimized for running containerized workloads and specifically prepared for running inside confidential VMs.
|
|
||||||
You can learn more about [the images](images.md) and how verified boot ensures their integrity during boot and beyond.
|
|
||||||
|
|
||||||
## Key management and cryptographic primitives
|
|
||||||
|
|
||||||
Encryption of data at-rest, in-transit, and in-use is the fundamental building block for confidential computing and Constellation. Learn more about the [keys and cryptographic primitives](keys.md) used in Constellation, [encrypted persistent storage](encrypted-storage.md), and [network encryption](networking.md).
|
|
||||||
|
|
||||||
## Observability
|
|
||||||
|
|
||||||
Observability in Kubernetes refers to the capability to troubleshoot issues using telemetry signals such as logs, metrics, and traces.
|
|
||||||
In the realm of Confidential Computing, it's crucial that observability aligns with confidentiality, necessitating careful implementation.
|
|
||||||
Learn more about the [observability capabilities in Constellation](./observability.md).
|
|
@ -1,131 +0,0 @@
|
|||||||
# Key management and cryptographic primitives
|
|
||||||
|
|
||||||
Constellation protects and isolates your cluster and workloads.
|
|
||||||
To that end, cryptography is the foundation that ensures the confidentiality and integrity of all components.
|
|
||||||
Evaluating the security and compliance of Constellation requires a precise understanding of the cryptographic primitives and keys used.
|
|
||||||
The following gives an overview of the architecture and explains the technical details.
|
|
||||||
|
|
||||||
## Confidential VMs
|
|
||||||
|
|
||||||
Confidential VM (CVM) technology comes with hardware and software components for memory encryption, isolation, and remote attestation.
|
|
||||||
For details on the implementations and cryptographic soundness, refer to the hardware vendors' documentation and advisories.
|
|
||||||
|
|
||||||
## Master secret
|
|
||||||
|
|
||||||
The master secret is the cryptographic material used for deriving the [_clusterID_](#cluster-identity) and the _key encryption key (KEK)_ for [storage encryption](#storage-encryption).
|
|
||||||
It's generated during the bootstrapping of a Constellation cluster.
|
|
||||||
It can either be managed by [Constellation](#constellation-managed-key-management) or an [external key management system](#user-managed-key-management).
|
|
||||||
In case of [recovery](#recovery-and-migration), the master secret allows to decrypt the state and recover a Constellation cluster.
|
|
||||||
|
|
||||||
## Cluster identity
|
|
||||||
|
|
||||||
The identity of a Constellation cluster is represented by cryptographic [measurements](attestation.md#runtime-measurements):
|
|
||||||
|
|
||||||
The **base measurements** represent the identity of a valid, uninitialized Constellation node.
|
|
||||||
They depend on the node image, but are otherwise the same for every Constellation cluster.
|
|
||||||
On node boot, they're determined using the CVM's attestation mechanism and [measured boot up to the read-only root filesystem](images.md).
|
|
||||||
|
|
||||||
The **clusterID** represents the identity of a single initialized Constellation cluster.
|
|
||||||
It's derived from the master secret and a cryptographically random salt and unique for every Constellation cluster.
|
|
||||||
The [Bootstrapper](microservices.md#bootstrapper) measures the _clusterID_ into its own PCR before executing any code not measured as part of the _base measurements_.
|
|
||||||
See [Node attestation](attestation.md#node-attestation) for details.
|
|
||||||
|
|
||||||
The remote attestation statement of a Constellation cluster combines the _base measurements_ and the _clusterID_ for a verifiable, unspoofable, unique identity.
|
|
||||||
|
|
||||||
## Network encryption
|
|
||||||
|
|
||||||
Constellation encrypts all cluster network communication using the [container network interface (CNI)](https://github.com/containernetworking/cni).
|
|
||||||
See [network encryption](networking.md) for more details.
|
|
||||||
|
|
||||||
The Cilium agent running on each node establishes a secure [WireGuard](https://www.wireguard.com/) tunnel between it and all other known nodes in the cluster.
|
|
||||||
Each node creates its own [Curve25519](http://cr.yp.to/ecdh.html) encryption key pair and distributes its public key via Kubernetes.
|
|
||||||
A node uses another node's public key to decrypt and encrypt traffic from and to Cilium-managed endpoints running on that node.
|
|
||||||
Connections are always encrypted peer-to-peer using [ChaCha20](http://cr.yp.to/chacha.html) with [Poly1305](http://cr.yp.to/mac.html).
|
|
||||||
WireGuard implements [forward secrecy with key rotation every 2 minutes](https://lists.zx2c4.com/pipermail/wireguard/2017-December/002141.html).
|
|
||||||
Cilium supports [key rotation](https://docs.cilium.io/en/stable/security/network/encryption-ipsec/#key-rotation) for the long-term node keys via Kubernetes secrets.
|
|
||||||
|
|
||||||
## Storage encryption
|
|
||||||
|
|
||||||
Constellation supports transparent encryption of persistent storage.
|
|
||||||
The Linux kernel's device mapper-based encryption features are used to encrypt the data on the block storage level.
|
|
||||||
Currently, the following primitives are used for block storage encryption:
|
|
||||||
|
|
||||||
- [dm-crypt](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-crypt.html)
|
|
||||||
- [dm-integrity](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-integrity.html)
|
|
||||||
|
|
||||||
Adding primitives for integrity protection in the CVM attacker model are under active development and will be available in a future version of Constellation.
|
|
||||||
See [encrypted storage](encrypted-storage.md) for more details.
|
|
||||||
|
|
||||||
As a cluster administrator, when creating a cluster, you can use the Constellation [installation program](orchestration.md) to select one of the following methods for key management:
|
|
||||||
|
|
||||||
- Constellation-managed key management
|
|
||||||
- User-managed key management
|
|
||||||
|
|
||||||
### Constellation-managed key management
|
|
||||||
|
|
||||||
#### Key material and key derivation
|
|
||||||
|
|
||||||
During the creation of a Constellation cluster, the cluster's master secret is used to derive a KEK.
|
|
||||||
This means creating two clusters with the same master secret will yield the same KEK.
|
|
||||||
Any data encryption key (DEK) is derived from the KEK via HKDF.
|
|
||||||
Note that the master secret is recommended to be unique for every cluster and shouldn't be reused (except in case of [recovering](../../workflows/recovery.md) a cluster).
|
|
||||||
|
|
||||||
#### State and storage
|
|
||||||
|
|
||||||
The KEK is derived from the master secret during the initialization.
|
|
||||||
Subsequently, all other key material is derived from the KEK.
|
|
||||||
Given the same KEK, any DEK can be derived deterministically from a given identifier.
|
|
||||||
Hence, there is no need to store DEKs. They can be derived on demand.
|
|
||||||
After the KEK was derived, it's stored in memory only and never leaves the CVM context.
|
|
||||||
|
|
||||||
#### Availability
|
|
||||||
|
|
||||||
Constellation-managed key management has the same availability as the underlying Kubernetes cluster.
|
|
||||||
Therefore, the KEK is stored in the [distributed Kubernetes etcd storage](https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/) to allow for unexpected but non-fatal (control-plane) node failure.
|
|
||||||
The etcd storage is backed by the encrypted and integrity protected [state disk](images.md#state-disk) of the nodes.
|
|
||||||
|
|
||||||
#### Recovery
|
|
||||||
|
|
||||||
Constellation clusters can be recovered in the event of a disaster, even when all node machines have been stopped and need to be rebooted.
|
|
||||||
For details on the process see the [recovery workflow](../../workflows/recovery.md).
|
|
||||||
|
|
||||||
### User-managed key management
|
|
||||||
|
|
||||||
User-managed key management is under active development and will be available soon.
|
|
||||||
In scenarios where constellation-managed key management isn't an option, this mode allows you to keep full control of your keys.
|
|
||||||
For example, compliance requirements may force you to keep your KEKs in an on-prem key management system (KMS).
|
|
||||||
|
|
||||||
During the creation of a Constellation cluster, you specify a KEK present in a remote KMS.
|
|
||||||
This follows the common scheme of "bring your own key" (BYOK).
|
|
||||||
Constellation will support several KMSs for managing the storage and access of your KEK.
|
|
||||||
Initially, it will support the following KMSs:
|
|
||||||
|
|
||||||
- [AWS KMS](https://aws.amazon.com/kms/)
|
|
||||||
- [GCP KMS](https://cloud.google.com/security-key-management)
|
|
||||||
- [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/#product-overview)
|
|
||||||
- [KMIP-compatible KMS](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=kmip)
|
|
||||||
|
|
||||||
Storing the keys in Cloud KMS of AWS, Azure, or GCP binds the key usage to the particular cloud identity access management (IAM).
|
|
||||||
In the future, Constellation will support remote attestation-based access policies for Cloud KMS once available.
|
|
||||||
Note that using a Cloud KMS limits the isolation and protection to the guarantees of the particular offering.
|
|
||||||
|
|
||||||
KMIP support allows you to use your KMIP-compatible on-prem KMS and keep full control over your keys.
|
|
||||||
This follows the common scheme of "hold your own key" (HYOK).
|
|
||||||
|
|
||||||
The KEK is used to encrypt per-data "data encryption keys" (DEKs).
|
|
||||||
DEKs are generated to encrypt your data before storing it on persistent storage.
|
|
||||||
After being encrypted by the KEK, the DEKs are stored on dedicated cloud storage for persistence.
|
|
||||||
Currently, Constellation supports the following cloud storage options:
|
|
||||||
|
|
||||||
- [AWS S3](https://aws.amazon.com/s3/)
|
|
||||||
- [GCP Cloud Storage](https://cloud.google.com/storage)
|
|
||||||
- [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/#overview)
|
|
||||||
|
|
||||||
The DEKs are only present in plaintext form in the encrypted main memory of the CVMs.
|
|
||||||
Similarly, the cryptographic operations for encrypting data before writing it to persistent storage are performed in the context of the CVMs.
|
|
||||||
|
|
||||||
#### Recovery and migration
|
|
||||||
|
|
||||||
In the case of a disaster, the KEK can be used to decrypt the DEKs locally and subsequently use them to decrypt and retrieve the data.
|
|
||||||
In case of migration, configuring the same KEK will provide seamless migration of data.
|
|
||||||
Thus, only the DEK storage needs to be transferred to the new cluster alongside the encrypted data for seamless migration.
|
|
@ -1,72 +0,0 @@
|
|||||||
# Microservices
|
|
||||||
|
|
||||||
Constellation takes care of bootstrapping and initializing a Confidential Kubernetes cluster.
|
|
||||||
During the lifetime of the cluster, it handles day 2 operations such as key management, remote attestation, and updates.
|
|
||||||
These features are provided by several microservices:
|
|
||||||
|
|
||||||
- The [Bootstrapper](microservices.md#bootstrapper) initializes a Constellation node and bootstraps the cluster
|
|
||||||
- The [JoinService](microservices.md#joinservice) joins new nodes to an existing cluster
|
|
||||||
- The [VerificationService](microservices.md#verificationservice) provides remote attestation functionality
|
|
||||||
- The [KeyService](microservices.md#keyservice) manages Constellation-internal keys
|
|
||||||
|
|
||||||
The relations between microservices are shown in the following diagram:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
subgraph admin [Admin's machine]
|
|
||||||
A[Constellation CLI]
|
|
||||||
end
|
|
||||||
subgraph img [Constellation OS image]
|
|
||||||
B[Constellation OS]
|
|
||||||
C[Bootstrapper]
|
|
||||||
end
|
|
||||||
subgraph Kubernetes
|
|
||||||
D[JoinService]
|
|
||||||
E[KeyService]
|
|
||||||
F[VerificationService]
|
|
||||||
end
|
|
||||||
A -- deploys -->
|
|
||||||
B -- starts --> C
|
|
||||||
C -- deploys --> D
|
|
||||||
C -- deploys --> E
|
|
||||||
C -- deploys --> F
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bootstrapper
|
|
||||||
|
|
||||||
The _Bootstrapper_ is the first microservice launched after booting a Constellation node image.
|
|
||||||
It sets up that machine as a Kubernetes node and integrates that node into the Kubernetes cluster.
|
|
||||||
To this end, the _Bootstrapper_ first downloads and verifies the [Kubernetes components](https://kubernetes.io/docs/concepts/overview/components/) at the configured versions.
|
|
||||||
The _Bootstrapper_ tries to find an existing cluster and if successful, communicates with the [JoinService](microservices.md#joinservice) to join the node.
|
|
||||||
Otherwise, it waits for an initialization request to create a new Kubernetes cluster.
|
|
||||||
|
|
||||||
## JoinService
|
|
||||||
|
|
||||||
The _JoinService_ runs as [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) on each control-plane node.
|
|
||||||
New nodes (at cluster start, or later through autoscaling) send a request to the service over [attested TLS (aTLS)](attestation.md#attested-tls-atls).
|
|
||||||
The _JoinService_ verifies the new node's certificate and attestation statement.
|
|
||||||
If attestation is successful, the new node is supplied with an encryption key from the [_KeyService_](microservices.md#keyservice) for its state disk, and a Kubernetes bootstrap token.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant New node
|
|
||||||
participant JoinService
|
|
||||||
New node->>JoinService: aTLS handshake (server side verification)
|
|
||||||
JoinService-->>New node: #
|
|
||||||
New node->>+JoinService: IssueJoinTicket(DiskUUID, NodeName, IsControlPlane)
|
|
||||||
JoinService->>+KeyService: GetDataKey(DiskUUID)
|
|
||||||
KeyService-->>-JoinService: DiskEncryptionKey
|
|
||||||
JoinService-->>-New node: DiskEncryptionKey, KubernetesJoinToken, ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## VerificationService
|
|
||||||
|
|
||||||
The _VerificationService_ runs as DaemonSet on each node.
|
|
||||||
It provides user-facing functionality for remote attestation during the cluster's lifetime via an endpoint for [verifying the cluster](attestation.md#cluster-attestation).
|
|
||||||
Read more about the hardware-based [attestation feature](attestation.md) of Constellation and how to [verify](../../workflows/verify-cluster.md) a cluster on the client side.
|
|
||||||
|
|
||||||
## KeyService
|
|
||||||
|
|
||||||
The _KeyService_ runs as DaemonSet on each control-plane node.
|
|
||||||
It implements the key management for the [storage encryption keys](keys.md#storage-encryption) in Constellation. These keys are used for the [state disk](images.md#state-disk) of each node and the [transparently encrypted storage](encrypted-storage.md) for Kubernetes.
|
|
||||||
Depending on wether the [constellation-managed](keys.md#constellation-managed-key-management) or [user-managed](keys.md#user-managed-key-management) mode is used, the _KeyService_ holds the key encryption key (KEK) directly or calls an external key management service (KMS) for key derivation respectively.
|
|
@ -1,22 +0,0 @@
|
|||||||
# Network encryption
|
|
||||||
|
|
||||||
Constellation encrypts all pod communication using the [container network interface (CNI)](https://github.com/containernetworking/cni).
|
|
||||||
To that end, Constellation deploys, configures, and operates the [Cilium](https://cilium.io/) CNI plugin.
|
|
||||||
Cilium provides [transparent encryption](https://docs.cilium.io/en/stable/security/network/encryption) for all cluster traffic using either IPSec or [WireGuard](https://www.wireguard.com/).
|
|
||||||
Currently, Constellation only supports WireGuard as the encryption engine.
|
|
||||||
You can read more about the cryptographic soundness of WireGuard [in their white paper](https://www.wireguard.com/papers/wireguard.pdf).
|
|
||||||
|
|
||||||
Cilium is actively working on implementing a feature called [`host-to-host`](https://github.com/cilium/cilium/pull/19401) encryption mode for WireGuard.
|
|
||||||
With `host-to-host`, all traffic between nodes will be tunneled via WireGuard (host-to-host, host-to-pod, pod-to-host, pod-to-pod).
|
|
||||||
Until the `host-to-host` feature is released, Constellation enables `pod-to-pod` encryption.
|
|
||||||
This mode encrypts all traffic between Kubernetes pods using WireGuard tunnels.
|
|
||||||
|
|
||||||
When using Cilium in the default setup but with encryption enabled, there is a [known issue](https://docs.cilium.io/en/v1.12/gettingstarted/encryption/#egress-traffic-to-not-yet-discovered-remote-endpoints-may-be-unencrypted)
|
|
||||||
that can cause pod-to-pod traffic to be unencrypted.
|
|
||||||
To mitigate this issue, Constellation adds a *strict* mode to Cilium's `pod-to-pod` encryption.
|
|
||||||
This mode changes the default behavior of traffic that's destined for an unknown endpoint to not be send out in plaintext, but instead being dropped.
|
|
||||||
The strict mode distinguishes between traffic that's send to a pod from traffic that's destined for a cluster-external endpoint by considering the pod's CIDR range.
|
|
||||||
|
|
||||||
Traffic originating from hosts isn't encrypted yet.
|
|
||||||
This mainly includes health checks from Kubernetes API server.
|
|
||||||
Also, traffic proxied over the API server via e.g. `kubectl port-forward` isn't encrypted.
|
|
@ -1,74 +0,0 @@
|
|||||||
# Observability
|
|
||||||
|
|
||||||
In Kubernetes, observability is the ability to gain insight into the behavior and performance of applications.
|
|
||||||
It helps identify and resolve issues more effectively, ensuring stability and performance of Kubernetes workloads, reducing downtime and outages, and improving efficiency.
|
|
||||||
The "three pillars of observability" are logs, metrics, and traces.
|
|
||||||
|
|
||||||
In the context of Confidential Computing, observability is a delicate subject and needs to be applied such that it doesn't leak any sensitive information.
|
|
||||||
The following gives an overview of where and how you can apply standard observability tools in Constellation.
|
|
||||||
|
|
||||||
## Cloud resource monitoring
|
|
||||||
|
|
||||||
While inaccessible, Constellation's nodes are still visible as black box VMs to the hypervisor.
|
|
||||||
Resource consumption, such as memory and CPU utilization, can be monitored from the outside and observed via the cloud platforms directly.
|
|
||||||
Similarly, other resources, such as storage and network and their respective metrics, are visible via the cloud platform.
|
|
||||||
|
|
||||||
## Metrics
|
|
||||||
|
|
||||||
Metrics are numeric representations of data measured over intervals of time. They're essential for understanding system health and gaining insights using telemetry signals.
|
|
||||||
|
|
||||||
By default, Constellation exposes the [metrics for Kubernetes system components](https://kubernetes.io/docs/concepts/cluster-administration/system-metrics/) inside the cluster.
|
|
||||||
Similarly, the [etcd metrics](https://etcd.io/docs/v3.5/metrics/) endpoints are exposed inside the cluster.
|
|
||||||
These [metrics endpoints can be disabled](https://kubernetes.io/docs/concepts/cluster-administration/system-metrics/#disabling-metrics).
|
|
||||||
|
|
||||||
You can collect these cluster-internal metrics via tools such as [Prometheus](https://prometheus.io/) or the [Elastic Stack](https://www.elastic.co/de/elastic-stack/).
|
|
||||||
|
|
||||||
Constellation's CNI Cilium also supports [metrics via Prometheus endpoints](https://docs.cilium.io/en/latest/observability/metrics/).
|
|
||||||
However, in Constellation, they're disabled by default and must be enabled first.
|
|
||||||
|
|
||||||
## Logs
|
|
||||||
|
|
||||||
Logs represent discrete events that usually describe what's happening with your service.
|
|
||||||
The payload is an actual message emitted from your system along with a metadata section containing a timestamp, labels, and tracking identifiers.
|
|
||||||
|
|
||||||
### System logs
|
|
||||||
|
|
||||||
Detailed system-level logs are accessible via `/var/log` and [journald](https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html) on the nodes directly.
|
|
||||||
They can be collected from there, for example, via [Filebeat and Logstash](https://www.elastic.co/guide/en/beats/filebeat/current/logstash-output.html), which are tools of the [Elastic Stack](https://www.elastic.co/de/elastic-stack/).
|
|
||||||
|
|
||||||
In case of an error during the initialization, the CLI automatically collects the [Bootstrapper](./microservices.md#bootstrapper) logs and returns these as a file for [troubleshooting](../../workflows/troubleshooting.md). Here is an example of such an event:
|
|
||||||
|
|
||||||
```shell-session
|
|
||||||
Cluster initialization failed. This error is not recoverable.
|
|
||||||
Terminate your cluster and try again.
|
|
||||||
Fetched bootstrapper logs are stored in "constellation-cluster.log"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Kubernetes logs
|
|
||||||
|
|
||||||
Constellation supports the [Kubernetes logging architecture](https://kubernetes.io/docs/concepts/cluster-administration/logging/).
|
|
||||||
By default, logs are written to the nodes' encrypted state disks.
|
|
||||||
These include the Pod and container logs and the [system component logs](https://kubernetes.io/docs/concepts/cluster-administration/logging/#system-component-logs).
|
|
||||||
|
|
||||||
[Constellation services](microservices.md) run as Pods inside the `kube-system` namespace and use the standard container logging mechanism.
|
|
||||||
The same applies for the [Cilium Pods](https://docs.cilium.io/en/latest/operations/troubleshooting/#logs).
|
|
||||||
|
|
||||||
You can collect logs from within the cluster via tools such as [Fluentd](https://github.com/fluent/fluentd), [Loki](https://github.com/grafana/loki), or the [Elastic Stack](https://www.elastic.co/de/elastic-stack/).
|
|
||||||
|
|
||||||
## Traces
|
|
||||||
|
|
||||||
Modern systems are implemented as interconnected complex and distributed microservices. Understanding request flows and system communications is challenging, mainly because all systems in a chain need to be modified to propagate tracing information. Distributed tracing is a new approach to increasing observability and understanding performance bottlenecks. A trace represents consecutive events that reflect an end-to-end request path in a distributed system.
|
|
||||||
|
|
||||||
Constellation supports [traces for Kubernetes system components](https://kubernetes.io/docs/concepts/cluster-administration/system-traces/).
|
|
||||||
By default, they're disabled and need to be enabled first.
|
|
||||||
|
|
||||||
Similarly, Cilium can be enabled to [export traces](https://cilium.io/use-cases/metrics-export/).
|
|
||||||
|
|
||||||
You can collect these traces via tools such as [Jaeger](https://www.jaegertracing.io/) or [Zipkin](https://zipkin.io/).
|
|
||||||
|
|
||||||
## Integrations
|
|
||||||
|
|
||||||
Platforms and SaaS solutions such as Datadog, logz.io, Dynatrace, or New Relic facilitate the observability challenge for Kubernetes and provide all-in-one SaaS solutions.
|
|
||||||
They install agents into the cluster that collect metrics, logs, and tracing information and upload them into the data lake of the platform.
|
|
||||||
Technically, the agent-based approach is compatible with Constellation, and attaching these platforms is straightforward.
|
|
||||||
However, you need to evaluate if the exported data might violate Constellation's compliance and privacy guarantees by uploading them to a third-party platform.
|
|
@ -1,83 +0,0 @@
|
|||||||
# Orchestrating Constellation clusters
|
|
||||||
|
|
||||||
You can use the CLI to create a cluster on the supported cloud platforms.
|
|
||||||
The CLI provisions the resources in your cloud environment and initiates the initialization of your cluster.
|
|
||||||
It uses a set of parameters and an optional configuration file to manage your cluster installation.
|
|
||||||
The CLI is also used for updating your cluster.
|
|
||||||
|
|
||||||
## Workspaces
|
|
||||||
|
|
||||||
Each Constellation cluster has an associated _workspace_.
|
|
||||||
The workspace is where data such as the Constellation state and config files are stored.
|
|
||||||
Each workspace is associated with a single cluster and configuration.
|
|
||||||
The CLI stores state in the local filesystem making the current directory the active workspace.
|
|
||||||
Multiple clusters require multiple workspaces, hence, multiple directories.
|
|
||||||
Note that every operation on a cluster always has to be performed from the directory associated with its workspace.
|
|
||||||
|
|
||||||
You may copy files from the workspace to other locations,
|
|
||||||
but you shouldn't move or delete them while the cluster is still being used.
|
|
||||||
The Constellation CLI takes care of managing the workspace.
|
|
||||||
Only when a cluster was terminated, and you are sure the files aren't needed anymore, should you remove a workspace.
|
|
||||||
|
|
||||||
## Cluster creation process
|
|
||||||
|
|
||||||
To allow for fine-grained configuration of your cluster and cloud environment, Constellation supports an extensive configuration file with strong defaults. [Generating the configuration file](../../workflows/config.md) is typically the first thing you do in the workspace.
|
|
||||||
|
|
||||||
Altogether, the following files are generated during the creation of a Constellation cluster and stored in the current workspace:
|
|
||||||
|
|
||||||
- a configuration file
|
|
||||||
- a state file
|
|
||||||
- a Base64-encoded master secret
|
|
||||||
- [Terraform artifacts](../../reference/terraform.md), stored in subdirectories
|
|
||||||
- a Kubernetes `kubeconfig` file.
|
|
||||||
|
|
||||||
After the initialization of your cluster, the CLI will provide you with a Kubernetes `kubeconfig` file.
|
|
||||||
This file grants you access to your Kubernetes cluster and configures the [kubectl](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) tool.
|
|
||||||
In addition, the cluster's [identifier](orchestration.md#post-installation-configuration) is returned and stored in the state file.
|
|
||||||
|
|
||||||
### Creation process details
|
|
||||||
|
|
||||||
1. The CLI `apply` command first creates the confidential VM (CVM) resources in your cloud environment and configures the network
|
|
||||||
2. Each CVM boots the Constellation node image and measures every component in the boot chain
|
|
||||||
3. The first microservice launched in each node is the [_Bootstrapper_](microservices.md#bootstrapper)
|
|
||||||
4. The _Bootstrapper_ waits until it either receives an initialization request or discovers an initialized cluster
|
|
||||||
5. The CLI then connects to the _Bootstrapper_ of a selected node, sends the configuration, and initiates the initialization of the cluster
|
|
||||||
6. The _Bootstrapper_ of **that** node [initializes the Kubernetes cluster](microservices.md#bootstrapper) and deploys the other Constellation [microservices](microservices.md) including the [_JoinService_](microservices.md#joinservice)
|
|
||||||
7. Subsequently, the _Bootstrappers_ of the other nodes discover the initialized cluster and send join requests to the _JoinService_
|
|
||||||
8. As part of the join request each node includes an attestation statement of its boot measurements as authentication
|
|
||||||
9. The _JoinService_ verifies the attestation statements and joins the nodes to the Kubernetes cluster
|
|
||||||
10. This process is repeated for every node joining the cluster later (e.g., through autoscaling)
|
|
||||||
|
|
||||||
## Post-installation configuration
|
|
||||||
|
|
||||||
Post-installation the CLI provides a configuration for [accessing the cluster using the Kubernetes API](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/).
|
|
||||||
The `kubeconfig` file provides the credentials and configuration for connecting and authenticating to the API server.
|
|
||||||
Once configured, orchestrate the Kubernetes cluster via `kubectl`.
|
|
||||||
|
|
||||||
After the initialization, the CLI will present you with a couple of tokens:
|
|
||||||
|
|
||||||
- The [_master secret_](keys.md#master-secret) (stored in the `constellation-mastersecret.json` file by default)
|
|
||||||
- The [_clusterID_](keys.md#cluster-identity) of your cluster in Base64 encoding
|
|
||||||
|
|
||||||
You can read more about these values and their meaning in the guide on [cluster identity](keys.md#cluster-identity).
|
|
||||||
|
|
||||||
The _master secret_ must be kept secret and can be used to [recover your cluster](../../workflows/recovery.md).
|
|
||||||
Instead of managing this secret manually, you can [use your key management solution of choice](keys.md#user-managed-key-management) with Constellation.
|
|
||||||
|
|
||||||
The _clusterID_ uniquely identifies a cluster and can be used to [verify your cluster](../../workflows/verify-cluster.md).
|
|
||||||
|
|
||||||
## Upgrades
|
|
||||||
|
|
||||||
Constellation images and microservices may need to be upgraded to new versions during the lifetime of a cluster.
|
|
||||||
Constellation implements a rolling update mechanism ensuring no downtime of the control or data plane.
|
|
||||||
You can upgrade a Constellation cluster with a single operation by using the CLI.
|
|
||||||
For step-by-step instructions on how to do this, refer to [Upgrade your cluster](../../workflows/upgrade.md).
|
|
||||||
|
|
||||||
### Attestation of upgrades
|
|
||||||
|
|
||||||
With every new image, corresponding measurements are released.
|
|
||||||
During an update procedure, the CLI provides new measurements to the [JoinService](microservices.md#joinservice) securely.
|
|
||||||
New measurements for an updated image are automatically pulled and verified by the CLI following the [supply chain security concept](attestation.md#chain-of-trust) of Constellation.
|
|
||||||
The [attestation section](attestation.md#cluster-facing-attestation) describes in detail how these measurements are then used by the JoinService for the attestation of nodes.
|
|
||||||
|
|
||||||
<!-- soon: As the [builds of the Constellation images are reproducible](attestation.md#chain-of-trust), the updated measurements are auditable by the customer. -->
|
|
@ -1,23 +0,0 @@
|
|||||||
# Versions and support policy
|
|
||||||
|
|
||||||
All components of Constellation use a three-digit version number of the form `v<MAJOR>.<MINOR>.<PATCH>`.
|
|
||||||
The components are released in lock step, usually on the first Tuesday of every month. This release primarily introduces new features, but may also include security or performance improvements. The `MINOR` version will be incremented as part of this release.
|
|
||||||
|
|
||||||
Additional `PATCH` releases may be created on demand, to fix security issues or bugs before the next `MINOR` release window.
|
|
||||||
|
|
||||||
New releases are published on [GitHub](https://github.com/edgelesssys/constellation/releases).
|
|
||||||
|
|
||||||
## Kubernetes support policy
|
|
||||||
|
|
||||||
Constellation is aligned to the [version support policy of Kubernetes](https://kubernetes.io/releases/version-skew-policy/#supported-versions), and therefore usually supports the most recent three minor versions.
|
|
||||||
When a new minor version of Kubernetes is released, support is added to the next Constellation release, and that version then supports four Kubernetes versions.
|
|
||||||
Subsequent Constellation releases drop support for the oldest (and deprecated) Kubernetes version.
|
|
||||||
|
|
||||||
The following Kubernetes versions are currently supported:
|
|
||||||
|
|
||||||
<!--AUTO_GENERATED_BY_BAZEL-->
|
|
||||||
<!--DO_NOT_EDIT-->
|
|
||||||
|
|
||||||
- v1.28.13
|
|
||||||
- v1.29.8
|
|
||||||
- v1.30.4
|
|
@ -298,62 +298,6 @@ const sidebars = {
|
|||||||
label: "Versions and support",
|
label: "Versions and support",
|
||||||
id: "architecture/versions",
|
id: "architecture/versions",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "category",
|
|
||||||
label: "Depricated",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Key concepts",
|
|
||||||
id: "architecture/old/key-concepts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Cluster orchestration",
|
|
||||||
id: "architecture/old/orchestration",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Versions and support",
|
|
||||||
id: "architecture/old/versions",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Microservices",
|
|
||||||
id: "architecture/old/microservices",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Attestation",
|
|
||||||
id: "architecture/old/attestation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Images",
|
|
||||||
id: "architecture/old/images",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Key management and cryptographic primitives",
|
|
||||||
id: "architecture/old/keys",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Encrypted persistent storage",
|
|
||||||
id: "architecture/old/encrypted-storage",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Networking",
|
|
||||||
id: "architecture/old/networking",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "doc",
|
|
||||||
label: "Observability",
|
|
||||||
id: "architecture/old/observability",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user