image: implement idempotent upload of os images

This commit is contained in:
Malte Poll 2023-04-21 10:47:07 +02:00 committed by Malte Poll
parent 17c45bc881
commit ee91d8b1cc
42 changed files with 4272 additions and 95 deletions

View File

@ -604,8 +604,8 @@ def go_dependencies():
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/Azure/azure-sdk-for-go/sdk/azcore",
sum = "h1:rTnT/Jrcm+figWlYz4Ixzt0SJVR2cMC8lvZcimipiEY=",
version = "v1.4.0",
sum = "h1:xGLAFFd9D3iLGxYiUGPdITSzsFmU1K8VtfuUHWAoN7M=",
version = "v1.5.0",
)
go_repository(
name = "com_github_azure_azure_sdk_for_go_sdk_azidentity",
@ -620,8 +620,8 @@ def go_dependencies():
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/Azure/azure-sdk-for-go/sdk/internal",
sum = "h1:leh5DwKv6Ihwi+h60uHtn6UWAxBbZ0q8DwQVMzf61zw=",
version = "v1.2.0",
sum = "h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=",
version = "v1.3.0",
)
go_repository(
@ -701,8 +701,8 @@ def go_dependencies():
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob",
sum = "h1:YvQv9Mz6T8oR5ypQOL6erY0Z5t71ak1uHV4QFokCOZk=",
version = "v0.6.1",
sum = "h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=",
version = "v1.0.0",
)
go_repository(
name = "com_github_azure_azure_service_bus_go",

6
go.mod
View File

@ -45,13 +45,13 @@ require (
cloud.google.com/go/secretmanager v1.10.0
cloud.google.com/go/storage v1.30.1
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.1.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.1.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/aws/aws-sdk-go-v2 v1.17.7
github.com/aws/aws-sdk-go-v2/config v1.18.19
@ -129,7 +129,7 @@ require (
cloud.google.com/go/iam v0.12.0 // indirect
cloud.google.com/go/longrunning v0.4.1 // indirect
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect

12
go.sum
View File

@ -89,12 +89,12 @@ github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 h1:rTnT/Jrcm+figWlYz4Ixzt0SJVR2cMC8lvZcimipiEY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0 h1:xGLAFFd9D3iLGxYiUGPdITSzsFmU1K8VtfuUHWAoN7M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 h1:uqM+VoHjVH6zdlkLF2b6O0ZANcHoj3rO0PoQ3jglUJA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2/go.mod h1:twTKAa1E6hLmSDjLhaCkbTMQKc7p/rNLU40rLxGEOCI=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 h1:leh5DwKv6Ihwi+h60uHtn6UWAxBbZ0q8DwQVMzf61zw=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0/go.mod h1:S78i9yTr4o/nXlH76bKjGUye9Z2wSxO5Tz7GoDr4vfI=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM=
@ -110,8 +110,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0/
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.1.0 h1:mk57wRUA8fyjFxVcPPGv4shLcWDXPFYokTJL9zJxQtE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.1.0/go.mod h1:mU96hbp8qJDA9OzTV1Ji7wCyPyaqC5kI6ZPsZfJ8sE4=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1 h1:YvQv9Mz6T8oR5ypQOL6erY0Z5t71ak1uHV4QFokCOZk=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1/go.mod h1:c6WvOhtmjNUWbLfOG1qxM/q0SPvQNSVJvolm+C52dIU=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=

View File

@ -60,9 +60,9 @@ require (
cloud.google.com/go/compute/metadata v0.2.3 // indirect
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.1.0 // indirect

View File

@ -81,12 +81,12 @@ github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 h1:rTnT/Jrcm+figWlYz4Ixzt0SJVR2cMC8lvZcimipiEY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0 h1:xGLAFFd9D3iLGxYiUGPdITSzsFmU1K8VtfuUHWAoN7M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 h1:uqM+VoHjVH6zdlkLF2b6O0ZANcHoj3rO0PoQ3jglUJA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2/go.mod h1:twTKAa1E6hLmSDjLhaCkbTMQKc7p/rNLU40rLxGEOCI=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 h1:leh5DwKv6Ihwi+h60uHtn6UWAxBbZ0q8DwQVMzf61zw=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v1.0.0 h1:BpGGvzarSyE7kQF1x1hptUcGmNzZEE3yYI+uqBSNRxk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v1.0.0/go.mod h1:1ijUM40peD7YK5MFEJja2wjjp4eimFNWv0NXoY3nsZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0.1 h1:H3g2mkmu105ON0c/Gqx3Bm+bzoIijLom8LmV9Gjn7X0=

0
image/BUILD.bazel Normal file
View File

View File

@ -57,7 +57,6 @@
jq \
mtools \
ovmf \
python3-crc32c \
python3-pefile \
python3-pyelftools \
python3-setuptools \
@ -177,6 +176,8 @@ secure-boot/azure/delete.sh --name "${AZURE_DISK_NAME}-setup-secure-boot"
## Upload to CSP
Warning! Never set `--version` to a value that is already used for a release image.
<details>
<summary>AWS</summary>
@ -188,19 +189,9 @@ secure-boot/azure/delete.sh --name "${AZURE_DISK_NAME}-setup-secure-boot"
- `pki_prod` is used for release images
```sh
# set these variables
export AWS_IMAGE_NAME= # e.g. "constellation-v1.0.0"
export PKI=${PWD}/pki
export AWS_REGION=eu-central-1
export AWS_REPLICATION_REGIONS="us-east-2"
export AWS_BUCKET=constellation-images
export AWS_EFIVARS_PATH=${PWD}/mkosi.output.aws/fedora~37/efivars.bin
export AWS_IMAGE_PATH=${PWD}/mkosi.output.aws/fedora~37/image.raw
export AWS_IMAGE_FILENAME=image-$(date +%s).raw
export AWS_JSON_OUTPUT=${PWD}/mkosi.output.aws/fedora~37/image-upload.json
secure-boot/aws/create_uefivars.sh "${AWS_EFIVARS_PATH}"
upload/upload_aws.sh
# Warning! Never set `--version` to a value that is already used for a release image.
# Instead, use a `ref` that corresponds to your branch name.
bazel run //image/upload -- aws --verbose --raw-image mkosi.output.aws/fedora~37/image.raw --variant "" --version ref/foo/stream/nightly/v2.7.0-pre-asdf
```
</details>
@ -216,20 +207,12 @@ upload/upload_aws.sh
- `pki_prod` is used for release images
```sh
# set these variables
export GCP_IMAGE_FAMILY= # e.g. "constellation"
export GCP_IMAGE_NAME= # e.g. "constellation-v1.0.0"
export PKI=${PWD}/pki
export GCP_PROJECT=constellation-images
export GCP_REGION=europe-west3
export GCP_BUCKET=constellation-images
export GCP_RAW_IMAGE_PATH=${PWD}/mkosi.output.gcp/fedora~37/image.raw
export GCP_IMAGE_FILENAME=$(date +%s).tar.gz
export GCP_IMAGE_PATH=${PWD}/mkosi.output.gcp/fedora~37/image.tar.gz
export GCP_JSON_OUTPUT=${PWD}/mkosi.output.gcp/fedora~37/image-upload.json
upload/pack.sh gcp ${GCP_RAW_IMAGE_PATH} ${GCP_IMAGE_PATH}
upload/upload_gcp.sh
# Warning! Never set `--version` to a value that is already used for a release image.
# Instead, use a `ref` that corresponds to your branch name.
bazel run //image/upload -- gcp --verbose --raw-image "${GCP_IMAGE_PATH}" --variant "sev-es" --version ref/foo/stream/nightly/v2.7.0-pre-asdf
```
</details>
@ -247,31 +230,12 @@ Note:
- Optional (if Secure Boot should be enabled) [Prepare virtual machine guest state (VMGS) with customized NVRAM or use existing VMGS blob](#azure-secure-boot)
```sh
# set these variables
export AZURE_GALLERY_NAME= # e.g. "Constellation"
export AZURE_IMAGE_DEFINITION= # e.g. "constellation"
export AZURE_IMAGE_VERSION= # e.g. "1.0.0"
# Set this variable to a path if you want to use Secure Boot.
# Otherwise, set it to export AZURE_VMGS_PATH=
export AZURE_VMGS_PATH= # e.g. nothing OR "path/to/ConfidentialVM.vmgs"
# AZURE_SECURITY_TYPE can be one of
# - "ConfidentialVMSupported" (ConfidentialVM with secure boot disabled),
# - "ConfidentialVM" (ConfidentialVM with Secure Boot) or
# - TrustedLaunch" (Trusted Launch with or without Secure Boot)
export AZURE_SECURITY_TYPE=ConfidentialVMSupported
export AZURE_RESOURCE_GROUP_NAME=constellation-images
export AZURE_REGION=northeurope
export AZURE_REPLICATION_REGIONS="northeurope eastus westeurope westus"
export AZURE_IMAGE_OFFER=constellation
export AZURE_SKU=${AZURE_IMAGE_DEFINITION}
export AZURE_PUBLISHER=edgelesssys
export AZURE_DISK_NAME=constellation-$(date +%s)
export AZURE_RAW_IMAGE_PATH=${PWD}/mkosi.output.azure/fedora~37/image.raw
export AZURE_IMAGE_PATH=${PWD}/mkosi.output.azure/fedora~37/image.vhd
export AZURE_JSON_OUTPUT=${PWD}/mkosi.output.azure/fedora~37/image-upload.json
upload/pack.sh azure "${AZURE_RAW_IMAGE_PATH}" "${AZURE_IMAGE_PATH}"
upload/upload_azure.sh -g --disk-name "${AZURE_DISK_NAME}" "${AZURE_VMGS_PATH}"
# Warning! Never set `--version` to a value that is already used for a release image.
# Instead, use a `ref` that corresponds to your branch name.
bazel run //image/upload -- azure --verbose --raw-image "${AZURE_IMAGE_PATH}" --variant "cvm" --version ref/foo/stream/nightly/v2.7.0-pre-asdf
```
</details>
@ -288,15 +252,9 @@ Note:
- Login to AWS (see [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html))
```sh
# set these variables
export REF= # e.g. feat-xyz (branch name encoded with dashes)
export STREAM= # e.g. "nightly", "debug", "stable" (depends on the type of image and if it is a release)
export IMAGE_VERSION= # e.g. v2.1.0" or output of pseudo-version tool
export OPENSTACK_BUCKET=cdn-constellation-backend
export OPENSTACK_BASE_URL="https://cdn.confidential.cloud"
export OPENSTACK_IMAGE_PATH=${PWD}/mkosi.output.qemu/fedora~37/image.raw
export OPENSTACK_JSON_OUTPUT=${PWD}/mkosi.output.qemu/fedora~37/image-upload.json
upload/upload_openstack.sh
# Warning! Never set `--version` to a value that is already used for a release image.
# Instead, use a `ref` that corresponds to your branch name.
bazel run //image/upload -- openstack --verbose --raw-image mkosi.output.openstack/fedora~37/image.raw --variant "sev" --version ref/foo/stream/nightly/v2.7.0-pre-asdf
```
</details>
@ -308,15 +266,9 @@ upload/upload_openstack.sh
- Login to AWS (see [here](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html))
```sh
# set these variables
export REF= # e.g. feat-xyz (branch name encoded with dashes)
export STREAM= # e.g. "nightly", "debug", "stable" (depends on the type of image and if it is a release)
export IMAGE_VERSION= # e.g. v2.1.0" or output of pseudo-version tool
export QEMU_BUCKET=cdn-constellation-backend
export QEMU_BASE_URL="https://cdn.confidential.cloud"
export QEMU_IMAGE_PATH=${PWD}/mkosi.output.qemu/fedora~37/image.raw
export QEMU_JSON_OUTPUT=${PWD}/mkosi.output.qemu/fedora~37/image-upload.json
upload/upload_qemu.sh
# Warning! Never set `--version` to a value that is already used for a release image.
# Instead, use a `ref` that corresponds to your branch name.
bazel run //image/upload -- qemu --verbose --raw-image mkosi.output.qemu/fedora~37/image.raw --variant "default" --version ref/foo/stream/nightly/v2.7.0-pre-asdf
```
</details>

18
image/upload/BUILD.bazel Normal file
View File

@ -0,0 +1,18 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "upload_lib",
srcs = ["upload.go"],
importpath = "github.com/edgelesssys/constellation/v2/image/upload",
visibility = ["//visibility:private"],
deps = [
"//image/upload/internal/cmd",
"@com_github_spf13_cobra//:cobra",
],
)
go_binary(
name = "upload",
embed = [":upload_lib"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,35 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "cmd",
srcs = [
"api.go",
"aws.go",
"azure.go",
"flags.go",
"gcp.go",
"must.go",
"nop.go",
"openstack.go",
"qemu.go",
"secureboot.go",
"upload.go",
],
importpath = "github.com/edgelesssys/constellation/v2/image/upload/internal/cmd",
visibility = ["//image/upload:__subpackages__"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/logger",
"//internal/osimage",
"//internal/osimage/archive",
"//internal/osimage/aws",
"//internal/osimage/azure",
"//internal/osimage/gcp",
"//internal/osimage/nop",
"//internal/osimage/secureboot",
"//internal/versionsapi",
"@com_github_spf13_afero//:afero",
"@com_github_spf13_cobra//:cobra",
"@org_uber_go_zap//zapcore",
],
)

View File

@ -0,0 +1,25 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"context"
"io"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
type archivist interface {
Archive(ctx context.Context,
version versionsapi.Version, csp, variant string, img io.Reader,
) (string, error)
}
type uploader interface {
Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error)
}

View File

@ -0,0 +1,97 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"fmt"
"io"
"os"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/archive"
awsupload "github.com/edgelesssys/constellation/v2/internal/osimage/aws"
"github.com/spf13/cobra"
)
// NewAWSCmd returns the command that uploads an OS image to AWS.
func NewAWSCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "aws",
Short: "Upload OS image to AWS",
Long: "Upload OS image to AWS.",
Args: cobra.ExactArgs(0),
RunE: runAWS,
}
cmd.Flags().String("aws-region", "eu-central-1", "AWS region used during AMI creation")
cmd.Flags().String("aws-bucket", "constellation-images", "S3 bucket used during AMI creation")
return cmd
}
func runAWS(cmd *cobra.Command, _ []string) error {
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
if len(workdir) > 0 {
must(os.Chdir(workdir))
}
flags, err := parseAWSFlags(cmd)
if err != nil {
return err
}
log := logger.New(logger.PlainLog, flags.logLevel)
log.Debugf("Parsed flags: %+v", flags)
archiveC, err := archive.New(cmd.Context(), flags.region, flags.bucket, log)
if err != nil {
return err
}
uploadC, err := awsupload.New(flags.awsRegion, flags.awsBucket, log)
if err != nil {
return fmt.Errorf("uploading image: %w", err)
}
file, err := os.Open(flags.rawImage)
if err != nil {
return fmt.Errorf("uploading image: opening image file %w", err)
}
defer file.Close()
size, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if _, err := file.Seek(0, io.SeekStart); err != nil {
return err
}
out := cmd.OutOrStdout()
if len(flags.out) > 0 {
outF, err := os.Create(flags.out)
if err != nil {
return fmt.Errorf("uploading image: opening output file %w", err)
}
defer outF.Close()
out = outF
}
sbDatabase, uefiVarStore, err := loadSecureBootKeys(flags.pki)
if err != nil {
return err
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -0,0 +1,98 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"fmt"
"io"
"os"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/archive"
azureupload "github.com/edgelesssys/constellation/v2/internal/osimage/azure"
"github.com/spf13/cobra"
)
// NewAzureCmd returns the command that uploads an OS image to Azure.
func NewAzureCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "azure",
Short: "Upload OS image to Azure",
Long: "Upload OS image to Azure.",
Args: cobra.ExactArgs(0),
RunE: runAzure,
}
cmd.Flags().String("az-subscription", "0d202bbb-4fa7-4af8-8125-58c269a05435", "Azure subscription to use")
cmd.Flags().String("az-location", "northeurope", "Azure location to use")
cmd.Flags().String("az-resource-group", "constellation-images", "Azure resource group to use")
return cmd
}
func runAzure(cmd *cobra.Command, _ []string) error {
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
if len(workdir) > 0 {
must(os.Chdir(workdir))
}
flags, err := parseAzureFlags(cmd)
if err != nil {
return err
}
log := logger.New(logger.PlainLog, flags.logLevel)
log.Debugf("Parsed flags: %+v", flags)
archiveC, err := archive.New(cmd.Context(), flags.region, flags.bucket, log)
if err != nil {
return err
}
uploadC, err := azureupload.New(flags.azSubscription, flags.azLocation, flags.azResourceGroup, log)
if err != nil {
return fmt.Errorf("uploading image: %w", err)
}
file, err := os.Open(flags.rawImage)
if err != nil {
return fmt.Errorf("uploading image: opening image file %w", err)
}
defer file.Close()
size, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if _, err := file.Seek(0, io.SeekStart); err != nil {
return err
}
out := cmd.OutOrStdout()
if len(flags.out) > 0 {
outF, err := os.Create(flags.out)
if err != nil {
return fmt.Errorf("uploading image: opening output file %w", err)
}
defer outF.Close()
out = outF
}
sbDatabase, uefiVarStore, err := loadSecureBootKeys(flags.pki)
if err != nil {
return err
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -0,0 +1,200 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"os"
"path/filepath"
"time"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
"github.com/spf13/cobra"
"go.uber.org/zap/zapcore"
)
type commonFlags struct {
rawImage string
pki string
provider cloudprovider.Provider
variant string
version versionsapi.Version
timestamp time.Time
region string
bucket string
out string
logLevel zapcore.Level
}
func parseCommonFlags(cmd *cobra.Command) (commonFlags, error) {
workspaceDir := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
rawImage, err := cmd.Flags().GetString("raw-image")
if err != nil {
return commonFlags{}, err
}
pki, err := cmd.Flags().GetString("pki")
if err != nil {
return commonFlags{}, err
}
if pki == "" {
pki = filepath.Join(workspaceDir, "image/pki")
}
variant, err := cmd.Flags().GetString("variant")
if err != nil {
return commonFlags{}, err
}
version, err := cmd.Flags().GetString("version")
if err != nil {
return commonFlags{}, err
}
ver, err := versionsapi.NewVersionFromShortPath(version, versionsapi.VersionKindImage)
if err != nil {
return commonFlags{}, err
}
timestamp, err := cmd.Flags().GetString("timestamp")
if err != nil {
return commonFlags{}, err
}
if timestamp == "" {
timestamp = time.Now().Format("2006-01-02T15:04:05Z07:00")
}
timestmp, err := time.Parse("2006-01-02T15:04:05Z07:00", timestamp)
if err != nil {
return commonFlags{}, err
}
region, err := cmd.Flags().GetString("region")
if err != nil {
return commonFlags{}, err
}
bucket, err := cmd.Flags().GetString("bucket")
if err != nil {
return commonFlags{}, err
}
out, err := cmd.Flags().GetString("out")
if err != nil {
return commonFlags{}, err
}
verbose, err := cmd.Flags().GetBool("verbose")
if err != nil {
return commonFlags{}, err
}
logLevel := zapcore.InfoLevel
if verbose {
logLevel = zapcore.DebugLevel
}
return commonFlags{
rawImage: rawImage,
pki: pki,
variant: variant,
version: ver,
timestamp: timestmp,
region: region,
bucket: bucket,
out: out,
logLevel: logLevel,
}, nil
}
type awsFlags struct {
commonFlags
awsRegion string
awsBucket string
}
func parseAWSFlags(cmd *cobra.Command) (awsFlags, error) {
common, err := parseCommonFlags(cmd)
if err != nil {
return awsFlags{}, err
}
awsRegion, err := cmd.Flags().GetString("aws-region")
if err != nil {
return awsFlags{}, err
}
awsBucket, err := cmd.Flags().GetString("aws-bucket")
if err != nil {
return awsFlags{}, err
}
common.provider = cloudprovider.AWS
return awsFlags{
commonFlags: common,
awsRegion: awsRegion,
awsBucket: awsBucket,
}, nil
}
type azureFlags struct {
commonFlags
azSubscription string
azLocation string
azResourceGroup string
}
func parseAzureFlags(cmd *cobra.Command) (azureFlags, error) {
common, err := parseCommonFlags(cmd)
if err != nil {
return azureFlags{}, err
}
azSubscription, err := cmd.Flags().GetString("az-subscription")
if err != nil {
return azureFlags{}, err
}
azLocation, err := cmd.Flags().GetString("az-location")
if err != nil {
return azureFlags{}, err
}
azResourceGroup, err := cmd.Flags().GetString("az-resource-group")
if err != nil {
return azureFlags{}, err
}
common.provider = cloudprovider.Azure
return azureFlags{
commonFlags: common,
azSubscription: azSubscription,
azLocation: azLocation,
azResourceGroup: azResourceGroup,
}, nil
}
type gcpFlags struct {
commonFlags
gcpProject string
gcpLocation string
gcpBucket string
}
func parseGCPFlags(cmd *cobra.Command) (gcpFlags, error) {
common, err := parseCommonFlags(cmd)
if err != nil {
return gcpFlags{}, err
}
gcpProject, err := cmd.Flags().GetString("gcp-project")
if err != nil {
return gcpFlags{}, err
}
gcpLocation, err := cmd.Flags().GetString("gcp-location")
if err != nil {
return gcpFlags{}, err
}
gcpBucket, err := cmd.Flags().GetString("gcp-bucket")
if err != nil {
return gcpFlags{}, err
}
common.provider = cloudprovider.GCP
return gcpFlags{
commonFlags: common,
gcpProject: gcpProject,
gcpLocation: gcpLocation,
gcpBucket: gcpBucket,
}, nil
}

View File

@ -0,0 +1,98 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"fmt"
"io"
"os"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/archive"
gcpupload "github.com/edgelesssys/constellation/v2/internal/osimage/gcp"
"github.com/spf13/cobra"
)
// NewGCPCommand returns the command that uploads an OS image to GCP.
func NewGCPCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "gcp",
Short: "Upload OS image to GCP",
Long: "Upload OS image to GCP.",
Args: cobra.ExactArgs(0),
RunE: runGCP,
}
cmd.Flags().String("gcp-project", "constellation-images", "GCP project to use")
cmd.Flags().String("gcp-location", "europe-west3", "GCP location to use")
cmd.Flags().String("gcp-bucket", "constellation-images", "GCP bucket to use")
return cmd
}
func runGCP(cmd *cobra.Command, _ []string) error {
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
if len(workdir) > 0 {
must(os.Chdir(workdir))
}
flags, err := parseGCPFlags(cmd)
if err != nil {
return err
}
log := logger.New(logger.PlainLog, flags.logLevel)
log.Debugf("Parsed flags: %+v", flags)
archiveC, err := archive.New(cmd.Context(), flags.region, flags.bucket, log)
if err != nil {
return err
}
uploadC, err := gcpupload.New(cmd.Context(), flags.gcpProject, flags.gcpLocation, flags.gcpBucket, log)
if err != nil {
return fmt.Errorf("uploading image: %w", err)
}
file, err := os.Open(flags.rawImage)
if err != nil {
return fmt.Errorf("uploading image: opening image file %w", err)
}
defer file.Close()
size, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if _, err := file.Seek(0, io.SeekStart); err != nil {
return err
}
out := cmd.OutOrStdout()
if len(flags.out) > 0 {
outF, err := os.Create(flags.out)
if err != nil {
return fmt.Errorf("uploading image: opening output file %w", err)
}
defer outF.Close()
out = outF
}
sbDatabase, uefiVarStore, err := loadSecureBootKeys(flags.pki)
if err != nil {
return err
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -0,0 +1,13 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
func must(err error) {
if err != nil {
panic(err)
}
}

View File

@ -0,0 +1,81 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"fmt"
"io"
"os"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/archive"
nopupload "github.com/edgelesssys/constellation/v2/internal/osimage/nop"
"github.com/spf13/cobra"
)
func runNOP(cmd *cobra.Command, provider cloudprovider.Provider, _ []string) error {
workdir := os.Getenv("BUILD_WORKING_DIRECTORY")
if len(workdir) > 0 {
must(os.Chdir(workdir))
}
flags, err := parseCommonFlags(cmd)
if err != nil {
return err
}
flags.provider = provider
log := logger.New(logger.PlainLog, flags.logLevel)
log.Debugf("Parsed flags: %+v", flags)
archiveC, err := archive.New(cmd.Context(), flags.region, flags.bucket, log)
if err != nil {
return err
}
uploadC := nopupload.New(log)
file, err := os.Open(flags.rawImage)
if err != nil {
return fmt.Errorf("uploading image: opening image file %w", err)
}
defer file.Close()
size, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if _, err := file.Seek(0, io.SeekStart); err != nil {
return err
}
out := cmd.OutOrStdout()
if len(flags.out) > 0 {
outF, err := os.Create(flags.out)
if err != nil {
return fmt.Errorf("uploading image: opening output file %w", err)
}
defer outF.Close()
out = outF
}
sbDatabase, uefiVarStore, err := loadSecureBootKeys(flags.pki)
if err != nil {
return err
}
uploadReq := &osimage.UploadRequest{
Provider: flags.provider,
Version: flags.version,
Variant: flags.variant,
SBDatabase: sbDatabase,
UEFIVarStore: uefiVarStore,
Size: size,
Timestamp: flags.timestamp,
Image: file,
}
return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out)
}

View File

@ -0,0 +1,29 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/spf13/cobra"
)
// NewOpenStackCmd returns the command that uploads an OS image to OpenStack.
func NewOpenStackCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "openstack",
Short: "Upload OS image to OpenStack",
Long: "Upload OS image to OpenStack.",
Args: cobra.ExactArgs(0),
RunE: runOpenStack,
}
return cmd
}
func runOpenStack(cmd *cobra.Command, args []string) error {
return runNOP(cmd, cloudprovider.OpenStack, args)
}

View File

@ -0,0 +1,29 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/spf13/cobra"
)
// NewQEMUCmd returns the command that uploads an OS image to QEMU.
func NewQEMUCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "qemu",
Short: "Upload OS image to QEMU",
Long: "Upload OS image to QEMU.",
Args: cobra.ExactArgs(0),
RunE: runQEMU,
}
return cmd
}
func runQEMU(cmd *cobra.Command, args []string) error {
return runNOP(cmd, cloudprovider.QEMU, args)
}

View File

@ -0,0 +1,44 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"fmt"
"path/filepath"
"github.com/edgelesssys/constellation/v2/internal/osimage/secureboot"
"github.com/spf13/afero"
)
func loadSecureBootKeys(basePath string) (secureboot.Database, secureboot.UEFIVarStore, error) {
platformKeyCert := filepath.Join(basePath, "PK.cer")
keyExchangeKeyCerts := []string{
filepath.Join(basePath, "KEK.cer"),
filepath.Join(basePath, "MicCorKEKCA2011_2011-06-24.crt"),
}
signatureDBCerts := []string{
filepath.Join(basePath, "db.cer"),
filepath.Join(basePath, "MicWinProPCA2011_2011-10-19.crt"),
filepath.Join(basePath, "MicCorUEFCA2011_2011-06-27.crt"),
}
sbDatabase, err := secureboot.DatabaseFromFiles(afero.NewOsFs(), platformKeyCert, keyExchangeKeyCerts, signatureDBCerts)
if err != nil {
return secureboot.Database{},
secureboot.UEFIVarStore{},
fmt.Errorf("preparing secure boot database: %w", err)
}
platformKeyESL := filepath.Join(basePath, "PK.esl")
keyExchangeKeyESL := filepath.Join(basePath, "KEK.esl")
signatureDBESL := filepath.Join(basePath, "db.esl")
uefiVarStore, err := secureboot.VarStoreFromFiles(afero.NewOsFs(), platformKeyESL, keyExchangeKeyESL, signatureDBESL, "")
if err != nil {
return secureboot.Database{},
secureboot.UEFIVarStore{},
fmt.Errorf("preparing secure boot variable store: %w", err)
}
return sbDatabase, uefiVarStore, nil
}

View File

@ -0,0 +1,66 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"context"
"encoding/json"
"fmt"
"io"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req *osimage.UploadRequest, out io.Writer) error {
// upload to S3 archive
archiveURL, err := archiveC.Archive(ctx, req.Version, req.Provider.String(), req.Variant, req.Image)
if err != nil {
return err
}
// rewind reader so we can read again
if _, err := req.Image.Seek(0, io.SeekStart); err != nil {
return err
}
// upload to CSP
imageReferences, err := uploadC.Upload(ctx, req)
if err != nil {
return err
}
if len(imageReferences) == 0 {
imageReferences = map[string]string{
req.Variant: archiveURL,
}
}
imageInfo := versionsapi.ImageInfo{
Ref: req.Version.Ref,
Stream: req.Version.Stream,
Version: req.Version.Version,
}
switch req.Provider {
case cloudprovider.AWS:
imageInfo.AWS = imageReferences
case cloudprovider.Azure:
imageInfo.Azure = imageReferences
case cloudprovider.GCP:
imageInfo.GCP = imageReferences
case cloudprovider.OpenStack:
imageInfo.OpenStack = imageReferences
case cloudprovider.QEMU:
imageInfo.QEMU = imageReferences
default:
return fmt.Errorf("uploading image: cloud provider %s is not yet supported", req.Provider.String())
}
if err := json.NewEncoder(out).Encode(imageInfo); err != nil {
return fmt.Errorf("uploading image: marshaling output: %w", err)
}
return nil
}

100
image/upload/upload.go Normal file
View File

@ -0,0 +1,100 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// upload uploads os images.
package main
import (
"context"
"fmt"
"os"
"os/signal"
"github.com/edgelesssys/constellation/v2/image/upload/internal/cmd"
"github.com/spf13/cobra"
)
func main() {
if err := execute(); err != nil {
os.Exit(1)
}
}
func execute() error {
rootCmd := newRootCmd()
ctx, cancel := signalContext(context.Background(), os.Interrupt)
defer cancel()
return rootCmd.ExecuteContext(ctx)
}
func newRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: "upload",
Short: "Uploads OS images to supported CSPs",
Long: "Uploads OS images to supported CSPs.",
PersistentPreRun: preRunRoot,
}
rootCmd.SetOut(os.Stdout)
rootCmd.PersistentFlags().String("raw-image", "", "Path to os image in CSP specific format that should be uploaded.")
rootCmd.PersistentFlags().String("pki", "", "Base path to the PKI (secure boot signing) files.")
rootCmd.PersistentFlags().String("variant", "", "Variant of the image being uploaded.")
rootCmd.PersistentFlags().String("version", "", "Shortname of the os image version.")
rootCmd.PersistentFlags().String("timestamp", "", "Optional timestamp to use for resource names. Uses format 2006-01-02T15:04:05Z07:00.")
rootCmd.PersistentFlags().String("region", "eu-central-1", "AWS region of the archive S3 bucket")
rootCmd.PersistentFlags().String("bucket", "cdn-constellation-backend", "S3 bucket name of the archive")
rootCmd.PersistentFlags().String("out", "", "Optional path to write the upload result to. If not set, the result is written to stdout.")
rootCmd.PersistentFlags().Bool("verbose", false, "Enable verbose output")
must(rootCmd.MarkPersistentFlagRequired("raw-image"))
must(rootCmd.MarkPersistentFlagRequired("variant"))
must(rootCmd.MarkPersistentFlagRequired("version"))
rootCmd.AddCommand(cmd.NewAWSCmd())
rootCmd.AddCommand(cmd.NewAzureCmd())
rootCmd.AddCommand(cmd.NewGCPCommand())
rootCmd.AddCommand(cmd.NewOpenStackCmd())
rootCmd.AddCommand(cmd.NewQEMUCmd())
return rootCmd
}
// signalContext returns a context that is canceled on the handed signal.
// The signal isn't watched after its first occurrence. Call the cancel
// function to ensure the internal goroutine is stopped and the signal isn't
// watched any longer.
func signalContext(ctx context.Context, sig os.Signal) (context.Context, context.CancelFunc) {
sigCtx, stop := signal.NotifyContext(ctx, sig)
done := make(chan struct{}, 1)
stopDone := make(chan struct{}, 1)
go func() {
defer func() { stopDone <- struct{}{} }()
defer stop()
select {
case <-sigCtx.Done():
fmt.Println(" Signal caught. Press ctrl+c again to terminate the program immediately.")
case <-done:
}
}()
cancelFunc := func() {
done <- struct{}{}
<-stopDone
}
return sigCtx, cancelFunc
}
func preRunRoot(cmd *cobra.Command, _ []string) {
cmd.SilenceUsage = true
}
func must(err error) {
if err != nil {
panic(err)
}
}

View File

@ -19,10 +19,14 @@ var (
"component": "constellation-kek",
}
// StorageTags are the default tags for kms client created storage solutions.
StorageTags = map[string]string{
"createdBy": "constellation-kms-client",
"component": "constellation-dek-store",
StorageTags = map[string]*string{
"createdBy": toPtr("constellation-kms-client"),
"component": toPtr("constellation-dek-store"),
}
// AWSS3Tag is the default tag string for kms client created AWS S3 storage solutions.
AWSS3Tag = "createdBy=constellation-kms-client&component=constellation-dek-store"
)
func toPtr[T any](v T) *T {
return &v
}

View File

@ -0,0 +1,13 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "osimage",
srcs = ["osimage.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage",
visibility = ["//:__subpackages__"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/osimage/secureboot",
"//internal/versionsapi",
],
)

View File

@ -0,0 +1,16 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "archive",
srcs = ["archive.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/archive",
visibility = ["//:__subpackages__"],
deps = [
"//internal/logger",
"//internal/versionsapi",
"@com_github_aws_aws_sdk_go_v2_config//:config",
"@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager",
"@com_github_aws_aws_sdk_go_v2_service_s3//:s3",
"@com_github_aws_aws_sdk_go_v2_service_s3//types",
],
)

View File

@ -0,0 +1,68 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// package archive is used to archive OS images in S3.
package archive
import (
"context"
"io"
"net/url"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
// Archivist uploads OS images to S3.
type Archivist struct {
uploadClient uploadClient
// bucket is the name of the S3 bucket to use.
bucket string
log *logger.Logger
}
// New creates a new Archivist.
func New(ctx context.Context, region, bucket string, log *logger.Logger) (*Archivist, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
s3client := s3.NewFromConfig(cfg)
uploadClient := s3manager.NewUploader(s3client)
return &Archivist{
uploadClient: uploadClient,
bucket: bucket,
log: log,
}, nil
}
// Archive reads the OS image in img and uploads it as key.
func (a *Archivist) Archive(ctx context.Context, version versionsapi.Version, csp, variant string, img io.Reader) (string, error) {
key, err := url.JoinPath(version.ArtifactPath(), version.Kind.String(), "csp", csp, variant, "image.raw")
if err != nil {
return "", err
}
a.log.Debugf("Archiving OS image %s %s %v to s3://%v/%v", csp, variant, version.ShortPath(), a.bucket, key)
_, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{
Bucket: &a.bucket,
Key: &key,
Body: img,
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
})
return baseURL + key, err
}
type uploadClient interface {
Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error)
}
const baseURL = "https://cdn.confidential.cloud/"

View File

@ -0,0 +1,21 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "aws",
srcs = ["awsupload.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/aws",
visibility = ["//:__subpackages__"],
deps = [
"//internal/logger",
"//internal/osimage",
"//internal/osimage/secureboot",
"//internal/versionsapi",
"@com_github_aws_aws_sdk_go_v2_config//:config",
"@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager",
"@com_github_aws_aws_sdk_go_v2_service_ec2//:ec2",
"@com_github_aws_aws_sdk_go_v2_service_ec2//types",
"@com_github_aws_aws_sdk_go_v2_service_s3//:s3",
"@com_github_aws_aws_sdk_go_v2_service_s3//types",
"@com_github_aws_smithy_go//:smithy-go",
],
)

View File

@ -0,0 +1,590 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// package aws implements uploading os images to aws.
package aws
import (
"context"
"errors"
"fmt"
"io"
"time"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/secureboot"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
// Uploader can upload and remove os images on GCP.
type Uploader struct {
region string
bucketName string
ec2 func(ctx context.Context, region string) (ec2API, error)
s3 func(ctx context.Context, region string) (s3API, error)
s3uploader func(ctx context.Context, region string) (s3UploaderAPI, error)
log *logger.Logger
}
// New creates a new Uploader.
func New(region, bucketName string, log *logger.Logger) (*Uploader, error) {
return &Uploader{
region: region,
bucketName: bucketName,
ec2: func(ctx context.Context, region string) (ec2API, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
return ec2.NewFromConfig(cfg), nil
},
s3: func(ctx context.Context, region string) (s3API, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
return s3.NewFromConfig(cfg), nil
},
s3uploader: func(ctx context.Context, region string) (s3UploaderAPI, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
return s3manager.NewUploader(s3.NewFromConfig(cfg)), nil
},
log: log,
}, nil
}
// Upload uploads an OS image to AWS.
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) {
blobName := fmt.Sprintf("image-%s-%s-%d.raw", req.Version.Stream, req.Version.Version, req.Timestamp.Unix())
imageName := imageName(req.Version, req.Timestamp)
allRegions := []string{u.region}
allRegions = append(allRegions, replicationRegions...)
// TODO(malt3): make this configurable
publish := true
amiIDs := make(map[string]string, len(allRegions))
if err := u.ensureBucket(ctx); err != nil {
return nil, fmt.Errorf("ensuring bucket %s exists: %w", u.bucketName, err)
}
// pre-cleaning
for _, region := range allRegions {
if err := u.ensureImageDeleted(ctx, imageName, region); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no image under the name %s in region %s: %w", imageName, region, err)
}
}
if err := u.ensureSnapshotDeleted(ctx, imageName, u.region); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no snapshot using the same name exists: %w", err)
}
if err := u.ensureBlobDeleted(ctx, blobName); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no blob using the same name exists: %w", err)
}
// create primary image
if err := u.uploadBlob(ctx, blobName, req.Image); err != nil {
return nil, fmt.Errorf("uploading image to s3: %w", err)
}
defer func() {
if err := u.ensureBlobDeleted(ctx, blobName); err != nil {
u.log.Errorf("post-cleaning: deleting temporary blob from s3", err)
}
}()
snapshotID, err := u.importSnapshot(ctx, blobName, imageName)
if err != nil {
return nil, fmt.Errorf("importing snapshot: %w", err)
}
primaryAMIID, err := u.createImageFromSnapshot(ctx, req.Version, imageName, snapshotID, req.UEFIVarStore)
if err != nil {
return nil, fmt.Errorf("creating image from snapshot: %w", err)
}
amiIDs[u.region] = primaryAMIID
if err := u.waitForImage(ctx, primaryAMIID, u.region); err != nil {
return nil, fmt.Errorf("waiting for primary image to become available: %w", err)
}
// replicate image
for _, region := range replicationRegions {
amiID, err := u.replicateImage(ctx, imageName, primaryAMIID, region)
if err != nil {
return nil, fmt.Errorf("replicating image to region %s: %w", region, err)
}
amiIDs[region] = amiID
}
// wait for replication, tag, publish
for _, region := range allRegions {
if err := u.waitForImage(ctx, amiIDs[region], region); err != nil {
return nil, fmt.Errorf("waiting for image to become available in region %s: %w", region, err)
}
if err := u.tagImageAndSnapshot(ctx, imageName, amiIDs[region], region); err != nil {
return nil, fmt.Errorf("tagging image in region %s: %w", region, err)
}
if !publish {
continue
}
if err := u.publishImage(ctx, amiIDs[region], region); err != nil {
return nil, fmt.Errorf("publishing image in region %s: %w", region, err)
}
}
return amiIDs, nil
}
func (u *Uploader) ensureBucket(ctx context.Context) error {
s3C, err := u.s3(ctx, u.region)
if err != nil {
return fmt.Errorf("determining if bucket %s exists: %w", u.bucketName, err)
}
_, err = s3C.HeadBucket(ctx, &s3.HeadBucketInput{
Bucket: &u.bucketName,
})
if err == nil {
u.log.Debugf("Bucket %s exists", u.bucketName)
return nil
}
var noSuchBucketErr *types.NoSuchBucket
if !errors.As(err, &noSuchBucketErr) {
return fmt.Errorf("determining if bucket %s exists: %w", u.bucketName, err)
}
u.log.Debugf("Creating bucket %s", u.bucketName)
_, err = s3C.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: &u.bucketName,
})
if err != nil {
return fmt.Errorf("creating bucket %s: %w", u.bucketName, err)
}
return nil
}
func (u *Uploader) uploadBlob(ctx context.Context, blobName string, img io.Reader) error {
u.log.Debugf("Uploading os image as %s", blobName)
uploadC, err := u.s3uploader(ctx, u.region)
if err != nil {
return err
}
_, err = uploadC.Upload(ctx, &s3.PutObjectInput{
Bucket: &u.bucketName,
Key: &blobName,
Body: img,
ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
})
return err
}
func (u *Uploader) ensureBlobDeleted(ctx context.Context, blobName string) error {
s3C, err := u.s3(ctx, u.region)
if err != nil {
return err
}
_, err = s3C.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &u.bucketName,
Key: &blobName,
})
var apiError smithy.APIError
if errors.As(err, &apiError) && apiError.ErrorCode() == "NotFound" {
u.log.Debugf("Blob %s in %s doesn't exist. Nothing to clean up.", blobName, u.bucketName)
return nil
}
if err != nil {
return err
}
u.log.Debugf("Deleting blob %s", blobName)
_, err = s3C.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: &u.bucketName,
Key: &blobName,
})
return err
}
func (u *Uploader) findSnapshots(ctx context.Context, snapshotName, region string) ([]string, error) {
ec2C, err := u.ec2(ctx, region)
if err != nil {
return nil, fmt.Errorf("creating ec2 client: %w", err)
}
snapshots, err := ec2C.DescribeSnapshots(ctx, &ec2.DescribeSnapshotsInput{
Filters: []ec2types.Filter{
{
Name: toPtr("tag:Name"),
Values: []string{snapshotName},
},
},
})
if err != nil {
return nil, fmt.Errorf("describing snapshots: %w", err)
}
var snapshotIDs []string
for _, s := range snapshots.Snapshots {
if s.SnapshotId == nil {
continue
}
snapshotIDs = append(snapshotIDs, *s.SnapshotId)
}
return snapshotIDs, nil
}
func (u *Uploader) importSnapshot(ctx context.Context, blobName, snapshotName string) (string, error) {
u.log.Debugf("Importing %s as snapshot %s", blobName, snapshotName)
ec2C, err := u.ec2(ctx, u.region)
if err != nil {
return "", fmt.Errorf("creating ec2 client: %w", err)
}
importResp, err := ec2C.ImportSnapshot(ctx, &ec2.ImportSnapshotInput{
ClientData: &ec2types.ClientData{
Comment: &snapshotName,
},
Description: &snapshotName,
DiskContainer: &ec2types.SnapshotDiskContainer{
Description: &snapshotName,
Format: toPtr(string(ec2types.DiskImageFormatRaw)),
UserBucket: &ec2types.UserBucket{
S3Bucket: &u.bucketName,
S3Key: &blobName,
},
},
})
if err != nil {
return "", fmt.Errorf("importing snapshot: %w", err)
}
if importResp.ImportTaskId == nil {
return "", fmt.Errorf("importing snapshot: no import task ID returned")
}
u.log.Debugf("Waiting for snapshot %s to be ready", snapshotName)
return waitForSnapshotImport(ctx, ec2C, *importResp.ImportTaskId)
}
func (u *Uploader) ensureSnapshotDeleted(ctx context.Context, snapshotName, region string) error {
ec2C, err := u.ec2(ctx, region)
if err != nil {
return fmt.Errorf("creating ec2 client: %w", err)
}
snapshots, err := u.findSnapshots(ctx, snapshotName, region)
if err != nil {
return fmt.Errorf("finding snapshots: %w", err)
}
for _, snapshot := range snapshots {
u.log.Debugf("Deleting snapshot %s in %s", snapshot, region)
_, err = ec2C.DeleteSnapshot(ctx, &ec2.DeleteSnapshotInput{
SnapshotId: toPtr(snapshot),
})
if err != nil {
return fmt.Errorf("deleting snapshot %s: %w", snapshot, err)
}
}
return nil
}
func (u *Uploader) createImageFromSnapshot(ctx context.Context, version versionsapi.Version, imageName, snapshotID string, uefiVarStore secureboot.UEFIVarStore) (string, error) {
u.log.Debugf("Creating image %s in %s", imageName, u.region)
ec2C, err := u.ec2(ctx, u.region)
if err != nil {
return "", fmt.Errorf("creating ec2 client: %w", err)
}
uefiData, err := uefiVarStore.ToAWS()
if err != nil {
return "", fmt.Errorf("creating uefi data: %w", err)
}
createReq, err := ec2C.RegisterImage(ctx, &ec2.RegisterImageInput{
Name: &imageName,
Architecture: ec2types.ArchitectureValuesX8664,
BlockDeviceMappings: []ec2types.BlockDeviceMapping{
{
DeviceName: toPtr("/dev/xvda"),
Ebs: &ec2types.EbsBlockDevice{
DeleteOnTermination: toPtr(true),
SnapshotId: &snapshotID,
},
},
},
BootMode: ec2types.BootModeValuesUefi,
Description: toPtr("Constellation " + version.ShortPath()),
EnaSupport: toPtr(true),
RootDeviceName: toPtr("/dev/xvda"),
TpmSupport: ec2types.TpmSupportValuesV20,
UefiData: &uefiData,
VirtualizationType: toPtr("hvm"),
})
if err != nil {
return "", fmt.Errorf("creating image: %w", err)
}
if createReq.ImageId == nil {
return "", fmt.Errorf("creating image: no image ID returned")
}
return *createReq.ImageId, nil
}
func (u *Uploader) replicateImage(ctx context.Context, imageName, amiID string, region string) (string, error) {
u.log.Debugf("Replicating image %s to %s", imageName, region)
ec2C, err := u.ec2(ctx, region)
if err != nil {
return "", fmt.Errorf("creating ec2 client: %w", err)
}
replicateReq, err := ec2C.CopyImage(ctx, &ec2.CopyImageInput{
Name: &imageName,
SourceImageId: &amiID,
SourceRegion: &u.region,
})
if err != nil {
return "", fmt.Errorf("replicating image: %w", err)
}
if replicateReq.ImageId == nil {
return "", fmt.Errorf("replicating image: no image ID returned")
}
return *replicateReq.ImageId, nil
}
func (u *Uploader) findImage(ctx context.Context, imageName, region string) (string, error) {
ec2C, err := u.ec2(ctx, region)
if err != nil {
return "", fmt.Errorf("creating ec2 client: %w", err)
}
snapshots, err := ec2C.DescribeImages(ctx, &ec2.DescribeImagesInput{
Filters: []ec2types.Filter{
{
Name: toPtr("name"),
Values: []string{imageName},
},
},
})
if err != nil {
return "", fmt.Errorf("describing images: %w", err)
}
if len(snapshots.Images) == 0 {
return "", errAMIDoesNotExist
}
if len(snapshots.Images) != 1 {
return "", fmt.Errorf("expected 1 image, got %d", len(snapshots.Images))
}
if snapshots.Images[0].ImageId == nil {
return "", fmt.Errorf("image ID is nil")
}
return *snapshots.Images[0].ImageId, nil
}
func (u *Uploader) waitForImage(ctx context.Context, amiID, region string) error {
u.log.Debugf("Waiting for image %s in %s to be created", amiID, region)
ec2C, err := u.ec2(ctx, region)
if err != nil {
return fmt.Errorf("creating ec2 client: %w", err)
}
waiter := ec2.NewImageAvailableWaiter(ec2C)
err = waiter.Wait(ctx, &ec2.DescribeImagesInput{
ImageIds: []string{amiID},
}, maxWait)
if err != nil {
return fmt.Errorf("waiting for image: %w", err)
}
return nil
}
func (u *Uploader) tagImageAndSnapshot(ctx context.Context, imageName, amiID, region string) error {
u.log.Debugf("Tagging backing snapshot of image %s in %s", amiID, region)
ec2C, err := u.ec2(ctx, region)
if err != nil {
return fmt.Errorf("creating ec2 client: %w", err)
}
snapshotID, err := getBackingSnapshotID(ctx, ec2C, amiID)
if err != nil {
return fmt.Errorf("getting backing snapshot ID: %w", err)
}
_, err = ec2C.CreateTags(ctx, &ec2.CreateTagsInput{
Resources: []string{amiID, snapshotID},
Tags: []ec2types.Tag{
{
Key: toPtr("Name"),
Value: toPtr(imageName),
},
},
})
if err != nil {
return fmt.Errorf("tagging ami and snapshot: %w", err)
}
return nil
}
func (u *Uploader) publishImage(ctx context.Context, imageName, region string) error {
u.log.Debugf("Publishing image %s in %s", imageName, region)
ec2C, err := u.ec2(ctx, region)
if err != nil {
return fmt.Errorf("creating ec2 client: %w", err)
}
_, err = ec2C.ModifyImageAttribute(ctx, &ec2.ModifyImageAttributeInput{
ImageId: &imageName,
LaunchPermission: &ec2types.LaunchPermissionModifications{
Add: []ec2types.LaunchPermission{
{
Group: ec2types.PermissionGroupAll,
},
},
},
})
if err != nil {
return fmt.Errorf("publishing image: %w", err)
}
return nil
}
func (u *Uploader) ensureImageDeleted(ctx context.Context, imageName, region string) error {
ec2C, err := u.ec2(ctx, region)
if err != nil {
return fmt.Errorf("creating ec2 client: %w", err)
}
amiID, err := u.findImage(ctx, imageName, region)
if err == errAMIDoesNotExist {
u.log.Debugf("Image %s in %s doesn't exist. Nothing to clean up.", imageName, region)
return nil
}
snapshotID, err := getBackingSnapshotID(ctx, ec2C, amiID)
if err == errAMIDoesNotExist {
u.log.Debugf("Image %s doesn't exist. Nothing to clean up.", amiID)
return nil
}
u.log.Debugf("Deleting image %s in %s with backing snapshot", amiID, region)
_, err = ec2C.DeregisterImage(ctx, &ec2.DeregisterImageInput{
ImageId: &amiID,
})
if err != nil {
return fmt.Errorf("deleting image: %w", err)
}
_, err = ec2C.DeleteSnapshot(ctx, &ec2.DeleteSnapshotInput{
SnapshotId: &snapshotID,
})
if err != nil {
return fmt.Errorf("deleting snapshot: %w", err)
}
return nil
}
func imageName(version versionsapi.Version, timestamp time.Time) string {
if version.Stream == "stable" {
return fmt.Sprintf("constellation-%s", version.Version)
}
return fmt.Sprintf("constellation-%s-%s-%s", version.Stream, version.Version, timestamp.Format(timestampFormat))
}
func waitForSnapshotImport(ctx context.Context, ec2C ec2API, importTaskID string) (string, error) {
for {
taskResp, err := ec2C.DescribeImportSnapshotTasks(ctx, &ec2.DescribeImportSnapshotTasksInput{
ImportTaskIds: []string{importTaskID},
})
if err != nil {
return "", fmt.Errorf("describing import snapshot task: %w", err)
}
if len(taskResp.ImportSnapshotTasks) == 0 {
return "", fmt.Errorf("describing import snapshot task: no tasks returned")
}
if taskResp.ImportSnapshotTasks[0].SnapshotTaskDetail == nil {
return "", fmt.Errorf("describing import snapshot task: no snapshot task detail returned")
}
if taskResp.ImportSnapshotTasks[0].SnapshotTaskDetail.Status == nil {
return "", fmt.Errorf("describing import snapshot task: no status returned")
}
switch *taskResp.ImportSnapshotTasks[0].SnapshotTaskDetail.Status {
case string(ec2types.SnapshotStateCompleted):
return *taskResp.ImportSnapshotTasks[0].SnapshotTaskDetail.SnapshotId, nil
case string(ec2types.SnapshotStateError):
return "", fmt.Errorf("importing snapshot: task failed")
}
time.Sleep(waitInterval)
}
}
func getBackingSnapshotID(ctx context.Context, ec2C ec2API, amiID string) (string, error) {
describeResp, err := ec2C.DescribeImages(ctx, &ec2.DescribeImagesInput{
ImageIds: []string{amiID},
})
if err != nil || len(describeResp.Images) == 0 {
return "", errAMIDoesNotExist
}
if len(describeResp.Images) != 1 {
return "", fmt.Errorf("describing image: expected 1 image, got %d", len(describeResp.Images))
}
image := describeResp.Images[0]
if len(image.BlockDeviceMappings) != 1 {
return "", fmt.Errorf("found %d block device mappings for image %s, expected 1", len(image.BlockDeviceMappings), amiID)
}
if image.BlockDeviceMappings[0].Ebs == nil {
return "", fmt.Errorf("image %s does not have an EBS block device mapping", amiID)
}
ebs := image.BlockDeviceMappings[0].Ebs
if ebs.SnapshotId == nil {
return "", fmt.Errorf("image %s does not have an EBS snapshot", amiID)
}
return *ebs.SnapshotId, nil
}
type ec2API interface {
DescribeImages(ctx context.Context, params *ec2.DescribeImagesInput,
optFns ...func(*ec2.Options),
) (*ec2.DescribeImagesOutput, error)
ModifyImageAttribute(ctx context.Context, params *ec2.ModifyImageAttributeInput,
optFns ...func(*ec2.Options),
) (*ec2.ModifyImageAttributeOutput, error)
RegisterImage(ctx context.Context, params *ec2.RegisterImageInput,
optFns ...func(*ec2.Options),
) (*ec2.RegisterImageOutput, error)
CopyImage(ctx context.Context, params *ec2.CopyImageInput, optFns ...func(*ec2.Options),
) (*ec2.CopyImageOutput, error)
DeregisterImage(ctx context.Context, params *ec2.DeregisterImageInput,
optFns ...func(*ec2.Options),
) (*ec2.DeregisterImageOutput, error)
ImportSnapshot(ctx context.Context, params *ec2.ImportSnapshotInput,
optFns ...func(*ec2.Options),
) (*ec2.ImportSnapshotOutput, error)
DescribeImportSnapshotTasks(ctx context.Context, params *ec2.DescribeImportSnapshotTasksInput,
optFns ...func(*ec2.Options),
) (*ec2.DescribeImportSnapshotTasksOutput, error)
DescribeSnapshots(ctx context.Context, params *ec2.DescribeSnapshotsInput,
optFns ...func(*ec2.Options),
) (*ec2.DescribeSnapshotsOutput, error)
DeleteSnapshot(ctx context.Context, params *ec2.DeleteSnapshotInput, optFns ...func(*ec2.Options),
) (*ec2.DeleteSnapshotOutput, error)
CreateTags(ctx context.Context, params *ec2.CreateTagsInput, optFns ...func(*ec2.Options),
) (*ec2.CreateTagsOutput, error)
}
type s3API interface {
HeadBucket(ctx context.Context, params *s3.HeadBucketInput, optFns ...func(*s3.Options),
) (*s3.HeadBucketOutput, error)
CreateBucket(ctx context.Context, params *s3.CreateBucketInput, optFns ...func(*s3.Options),
) (*s3.CreateBucketOutput, error)
HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options),
) (*s3.HeadObjectOutput, error)
DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options),
) (*s3.DeleteObjectOutput, error)
}
type s3UploaderAPI interface {
Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader),
) (*s3manager.UploadOutput, error)
}
func toPtr[T any](v T) *T {
return &v
}
const (
waitInterval = 15 * time.Second
maxWait = 15 * time.Minute
timestampFormat = "20060102150405"
)
var (
errAMIDoesNotExist = errors.New("ami does not exist")
replicationRegions = []string{"us-east-2", "ap-south-1"}
)

View File

@ -0,0 +1,21 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "azure",
srcs = [
"azureupload.go",
"disktype_string.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/azure",
visibility = ["//:__subpackages__"],
deps = [
"//internal/logger",
"//internal/osimage",
"//internal/versionsapi",
"@com_github_azure_azure_sdk_for_go_sdk_azcore//runtime",
"@com_github_azure_azure_sdk_for_go_sdk_azidentity//:azidentity",
"@com_github_azure_azure_sdk_for_go_sdk_resourcemanager_compute_armcompute_v4//:armcompute",
"@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//blob",
"@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//pageblob",
],
)

View File

@ -0,0 +1,701 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// package azure implements uploading os images to azure.
package azure
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
armcomputev4 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
// Uploader can upload and remove os images on Azure.
type Uploader struct {
subscription string
location string
resourceGroup string
pollingFrequency time.Duration
disks azureDiskAPI
managedImages azureManagedImageAPI
blob sasBlobUploader
galleries azureGalleriesAPI
image azureGalleriesImageAPI
imageVersions azureGalleriesImageVersionAPI
communityVersions azureCommunityGalleryImageVersionAPI
log *logger.Logger
}
// New creates a new Uploader.
func New(subscription, location, resourceGroup string, log *logger.Logger) (*Uploader, error) {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, err
}
diskClient, err := armcomputev4.NewDisksClient(subscription, cred, nil)
if err != nil {
return nil, err
}
managedImagesClient, err := armcomputev4.NewImagesClient(subscription, cred, nil)
if err != nil {
return nil, err
}
galleriesClient, err := armcomputev4.NewGalleriesClient(subscription, cred, nil)
if err != nil {
return nil, err
}
galleriesImageClient, err := armcomputev4.NewGalleryImagesClient(subscription, cred, nil)
if err != nil {
return nil, err
}
galleriesImageVersionClient, err := armcomputev4.NewGalleryImageVersionsClient(subscription, cred, nil)
if err != nil {
return nil, err
}
communityImageVersionClient, err := armcomputev4.NewCommunityGalleryImageVersionsClient(subscription, cred, nil)
if err != nil {
return nil, err
}
return &Uploader{
subscription: subscription,
location: location,
resourceGroup: resourceGroup,
pollingFrequency: pollingFrequency,
disks: diskClient,
managedImages: managedImagesClient,
blob: func(sasBlobURL string) (azurePageblobAPI, error) {
return pageblob.NewClientWithNoCredential(sasBlobURL, nil)
},
galleries: galleriesClient,
image: galleriesImageClient,
imageVersions: galleriesImageVersionClient,
communityVersions: communityImageVersionClient,
log: log,
}, nil
}
// Upload uploads an OS image to Azure.
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) {
formattedTime := req.Timestamp.Format(timestampFormat)
diskName := fmt.Sprintf("constellation-%s-%s-%s", req.Version.Stream, formattedTime, req.Variant)
var sigName string
switch req.Version.Stream {
case "stable":
sigName = sigNameStable
case "debug":
sigName = sigNameDebug
default:
sigName = sigNameDefault
}
definitionName := imageOffer(req.Version)
versionName, err := imageVersion(req.Version, req.Timestamp)
if err != nil {
return nil, fmt.Errorf("determining image version name: %w", err)
}
// ensure new image can be uploaded by deleting existing resources using the same name
if err := u.ensureImageVersionDeleted(ctx, sigName, definitionName, versionName); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no image version using the same name exists: %w", err)
}
if err := u.ensureManagedImageDeleted(ctx, diskName); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no managed image using the same name exists: %w", err)
}
if err := u.ensureDiskDeleted(ctx, diskName); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no temporary disk using the same name exists: %w", err)
}
diskID, err := u.createDisk(ctx, diskName, DiskTypeNormal, req.Image, nil, req.Size)
if err != nil {
return nil, fmt.Errorf("creating disk: %w", err)
}
defer func() {
// cleanup temp disk
err := u.ensureDiskDeleted(ctx, diskName)
if err != nil {
u.log.Errorf("post-cleaning: deleting disk image: %v", err)
}
}()
managedImageID, err := u.createManagedImage(ctx, diskName, diskID)
if err != nil {
return nil, fmt.Errorf("creating managed image: %w", err)
}
if err := u.ensureSIG(ctx, sigName); err != nil {
return nil, fmt.Errorf("ensuring sig exists: %w", err)
}
if err := u.ensureImageDefinition(ctx, sigName, definitionName, req.Version, req.Variant); err != nil {
return nil, fmt.Errorf("ensuring image definition exists: %w", err)
}
unsharedImageVersionID, err := u.createImageVersion(ctx, sigName, definitionName, versionName, managedImageID)
if err != nil {
return nil, fmt.Errorf("creating image version: %w", err)
}
imageReference, err := u.getImageReference(ctx, sigName, definitionName, versionName, unsharedImageVersionID)
if err != nil {
return nil, fmt.Errorf("getting image reference: %w", err)
}
return map[string]string{
req.Variant: imageReference,
}, nil
}
// createDisk creates and initializes (uploads contents of) an azure disk.
func (u *Uploader) createDisk(ctx context.Context, diskName string, diskType DiskType, img io.ReadSeeker, vmgs io.ReadSeeker, size int64) (string, error) {
u.log.Debugf("Creating disk %s in %s", diskName, u.resourceGroup)
if diskType == DiskTypeWithVMGS && vmgs == nil {
return "", errors.New("cannot create disk with vmgs: vmgs reader is nil")
}
var createOption armcomputev4.DiskCreateOption
var requestVMGSSAS bool
switch diskType {
case DiskTypeNormal:
createOption = armcomputev4.DiskCreateOptionUpload
case DiskTypeWithVMGS:
createOption = armcomputev4.DiskCreateOptionUploadPreparedSecure
requestVMGSSAS = true
}
disk := armcomputev4.Disk{
Location: &u.location,
Properties: &armcomputev4.DiskProperties{
CreationData: &armcomputev4.CreationData{
CreateOption: &createOption,
UploadSizeBytes: toPtr(size),
},
HyperVGeneration: toPtr(armcomputev4.HyperVGenerationV2),
OSType: toPtr(armcomputev4.OperatingSystemTypesLinux),
},
}
createPoller, err := u.disks.BeginCreateOrUpdate(ctx, u.resourceGroup, diskName, disk, &armcomputev4.DisksClientBeginCreateOrUpdateOptions{})
if err != nil {
return "", fmt.Errorf("creating disk: %w", err)
}
createdDisk, err := createPoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency})
if err != nil {
return "", fmt.Errorf("waiting for disk to be created: %w", err)
}
u.log.Debugf("Granting temporary upload permissions via SAS token")
accessGrant := armcomputev4.GrantAccessData{
Access: toPtr(armcomputev4.AccessLevelWrite),
DurationInSeconds: toPtr(int32(uploadAccessDuration)),
GetSecureVMGuestStateSAS: &requestVMGSSAS,
}
accessPoller, err := u.disks.BeginGrantAccess(ctx, u.resourceGroup, diskName, accessGrant, &armcomputev4.DisksClientBeginGrantAccessOptions{})
if err != nil {
return "", fmt.Errorf("generating disk sas token: %w", err)
}
accesPollerResp, err := accessPoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency})
if err != nil {
return "", fmt.Errorf("waiting for sas token: %w", err)
}
if requestVMGSSAS {
u.log.Debugf("Uploading vmgs")
vmgsSize, err := vmgs.Seek(0, io.SeekEnd)
if err != nil {
return "", err
}
if _, err := vmgs.Seek(0, io.SeekStart); err != nil {
return "", err
}
if accesPollerResp.SecurityDataAccessSAS == nil {
return "", errors.New("uploading vmgs: grant access returned no vmgs sas")
}
if err := uploadBlob(ctx, *accesPollerResp.SecurityDataAccessSAS, vmgs, vmgsSize, u.blob); err != nil {
return "", fmt.Errorf("uploading vmgs: %w", err)
}
}
u.log.Debugf("Uploading os image")
if accesPollerResp.AccessSAS == nil {
return "", errors.New("uploading disk: grant access returned no disk sas")
}
if err := uploadBlob(ctx, *accesPollerResp.AccessSAS, img, size, u.blob); err != nil {
return "", fmt.Errorf("uploading image: %w", err)
}
revokePoller, err := u.disks.BeginRevokeAccess(ctx, u.resourceGroup, diskName, &armcomputev4.DisksClientBeginRevokeAccessOptions{})
if err != nil {
return "", fmt.Errorf("revoking disk sas token: %w", err)
}
if _, err := revokePoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency}); err != nil {
return "", fmt.Errorf("waiting for sas token revocation: %w", err)
}
if createdDisk.ID == nil {
return "", errors.New("created disk has no id")
}
return *createdDisk.ID, nil
}
func (u *Uploader) ensureDiskDeleted(ctx context.Context, diskName string) error {
_, err := u.disks.Get(ctx, u.resourceGroup, diskName, &armcomputev4.DisksClientGetOptions{})
if err != nil {
u.log.Debugf("Disk %s in %s doesn't exist. Nothing to clean up.", diskName, u.resourceGroup)
return nil
}
u.log.Debugf("Deleting disk %s in %s", diskName, u.resourceGroup)
deletePoller, err := u.disks.BeginDelete(ctx, u.resourceGroup, diskName, &armcomputev4.DisksClientBeginDeleteOptions{})
if err != nil {
return fmt.Errorf("deleting disk: %w", err)
}
if _, err = deletePoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency}); err != nil {
return fmt.Errorf("waiting for disk to be deleted: %w", err)
}
return nil
}
func (u *Uploader) createManagedImage(ctx context.Context, imageName string, diskID string) (string, error) {
u.log.Debugf("Creating managed image %s in %s", imageName, u.resourceGroup)
image := armcomputev4.Image{
Location: &u.location,
Properties: &armcomputev4.ImageProperties{
HyperVGeneration: toPtr(armcomputev4.HyperVGenerationTypesV2),
StorageProfile: &armcomputev4.ImageStorageProfile{
OSDisk: &armcomputev4.ImageOSDisk{
OSState: toPtr(armcomputev4.OperatingSystemStateTypesGeneralized),
OSType: toPtr(armcomputev4.OperatingSystemTypesLinux),
ManagedDisk: &armcomputev4.SubResource{
ID: &diskID,
},
},
},
},
}
createPoller, err := u.managedImages.BeginCreateOrUpdate(
ctx, u.resourceGroup, imageName, image,
&armcomputev4.ImagesClientBeginCreateOrUpdateOptions{},
)
if err != nil {
return "", fmt.Errorf("creating managed image: %w", err)
}
createdImage, err := createPoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency})
if err != nil {
return "", fmt.Errorf("waiting for image to be created: %w", err)
}
if createdImage.ID == nil {
return "", errors.New("created image has no id")
}
return *createdImage.ID, nil
}
func (u *Uploader) ensureManagedImageDeleted(ctx context.Context, imageName string) error {
_, err := u.managedImages.Get(ctx, u.resourceGroup, imageName, &armcomputev4.ImagesClientGetOptions{})
if err != nil {
u.log.Debugf("Managed image %s in %s doesn't exist. Nothing to clean up.", imageName, u.resourceGroup)
return nil
}
u.log.Debugf("Deleting managed image %s in %s", imageName, u.resourceGroup)
deletePoller, err := u.managedImages.BeginDelete(ctx, u.resourceGroup, imageName, &armcomputev4.ImagesClientBeginDeleteOptions{})
if err != nil {
return fmt.Errorf("deleting image: %w", err)
}
if _, err = deletePoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency}); err != nil {
return fmt.Errorf("waiting for image to be deleted: %w", err)
}
return nil
}
// ensureSIG creates a SIG if it does not exist yet.
func (u *Uploader) ensureSIG(ctx context.Context, sigName string) error {
_, err := u.galleries.Get(ctx, u.resourceGroup, sigName, &armcomputev4.GalleriesClientGetOptions{})
if err == nil {
u.log.Debugf("Image gallery %s in %s exists", sigName, u.resourceGroup)
return nil
}
u.log.Debugf("Creating image gallery %s in %s", sigName, u.resourceGroup)
gallery := armcomputev4.Gallery{
Location: &u.location,
}
createPoller, err := u.galleries.BeginCreateOrUpdate(ctx, u.resourceGroup, sigName, gallery,
&armcomputev4.GalleriesClientBeginCreateOrUpdateOptions{},
)
if err != nil {
return fmt.Errorf("creating image gallery: %w", err)
}
if _, err = createPoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency}); err != nil {
return fmt.Errorf("waiting for image gallery to be created: %w", err)
}
return nil
}
// ensureImageDefinition creates an image definition (component of a SIG) if it does not exist yet.
func (u *Uploader) ensureImageDefinition(ctx context.Context, sigName, definitionName string, version versionsapi.Version, variant string) error {
_, err := u.image.Get(ctx, u.resourceGroup, sigName, definitionName, &armcomputev4.GalleryImagesClientGetOptions{})
if err == nil {
u.log.Debugf("Image definition %s/%s in %s exists", sigName, definitionName, u.resourceGroup)
return nil
}
u.log.Debugf("Creating image definition %s/%s in %s", sigName, definitionName, u.resourceGroup)
var securityType string
// TODO(malt3): This needs to allow the *Supported or the normal variant
// based on wether a VMGS was provided or not.
// VMGS provided: ConfidentialVM
// No VMGS provided: ConfidentialVMSupported
switch strings.ToLower(variant) {
case "cvm":
securityType = string("ConfidentialVMSupported")
case "trustedlaunch":
securityType = string(armcomputev4.SecurityTypesTrustedLaunch)
}
offer := imageOffer(version)
galleryImage := armcomputev4.GalleryImage{
Location: &u.location,
Properties: &armcomputev4.GalleryImageProperties{
Identifier: &armcomputev4.GalleryImageIdentifier{
Offer: &offer,
Publisher: toPtr(imageDefinitionPublisher),
SKU: toPtr(imageDefinitionSKU),
},
OSState: toPtr(armcomputev4.OperatingSystemStateTypesGeneralized),
OSType: toPtr(armcomputev4.OperatingSystemTypesLinux),
Architecture: toPtr(armcomputev4.ArchitectureX64),
Features: []*armcomputev4.GalleryImageFeature{
{
Name: toPtr("SecurityType"),
Value: &securityType,
},
},
HyperVGeneration: toPtr(armcomputev4.HyperVGenerationV2),
},
}
createPoller, err := u.image.BeginCreateOrUpdate(ctx, u.resourceGroup, sigName, definitionName, galleryImage,
&armcomputev4.GalleryImagesClientBeginCreateOrUpdateOptions{},
)
if err != nil {
return fmt.Errorf("creating image definition: %w", err)
}
if _, err = createPoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency}); err != nil {
return fmt.Errorf("waiting for image definition to be created: %w", err)
}
return nil
}
func (u *Uploader) createImageVersion(ctx context.Context, sigName, definitionName, versionName, imageID string) (string, error) {
u.log.Debugf("Creating image version %s/%s/%s in %s", sigName, definitionName, versionName, u.resourceGroup)
imageVersion := armcomputev4.GalleryImageVersion{
Location: &u.location,
Properties: &armcomputev4.GalleryImageVersionProperties{
StorageProfile: &armcomputev4.GalleryImageVersionStorageProfile{
OSDiskImage: &armcomputev4.GalleryOSDiskImage{
HostCaching: toPtr(armcomputev4.HostCachingReadOnly),
},
Source: &armcomputev4.GalleryArtifactVersionFullSource{
ID: &imageID,
},
},
PublishingProfile: &armcomputev4.GalleryImageVersionPublishingProfile{
ReplicaCount: toPtr[int32](1),
ReplicationMode: toPtr(armcomputev4.ReplicationModeFull),
TargetRegions: targetRegions,
},
},
}
createPoller, err := u.imageVersions.BeginCreateOrUpdate(ctx, u.resourceGroup, sigName, definitionName, versionName, imageVersion,
&armcomputev4.GalleryImageVersionsClientBeginCreateOrUpdateOptions{},
)
if err != nil {
return "", fmt.Errorf("creating image version: %w", err)
}
createdImage, err := createPoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency})
if err != nil {
return "", fmt.Errorf("waiting for image version to be created: %w", err)
}
if createdImage.ID == nil {
return "", errors.New("created image has no id")
}
return *createdImage.ID, nil
}
func (u *Uploader) ensureImageVersionDeleted(ctx context.Context, sigName, definitionName, versionName string) error {
_, err := u.imageVersions.Get(ctx, u.resourceGroup, sigName, definitionName, versionName, &armcomputev4.GalleryImageVersionsClientGetOptions{})
if err != nil {
u.log.Debugf("Image version %s in %s/%s/%s doesn't exist. Nothing to clean up.", versionName, u.resourceGroup, sigName, definitionName)
return nil
}
u.log.Debugf("Deleting image version %s in %s/%s/%s", versionName, u.resourceGroup, sigName, definitionName)
deletePoller, err := u.imageVersions.BeginDelete(ctx, u.resourceGroup, sigName, definitionName, versionName, &armcomputev4.GalleryImageVersionsClientBeginDeleteOptions{})
if err != nil {
return fmt.Errorf("deleting image version: %w", err)
}
if _, err = deletePoller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: u.pollingFrequency}); err != nil {
return fmt.Errorf("waiting for image version to be deleted: %w", err)
}
return nil
}
// getImageReference returns the image reference to use for the image version.
// If the shared image gallery is a community gallery, the community identifier is returned.
// Otherwise, the unshared identifier is returned.
func (u *Uploader) getImageReference(ctx context.Context, sigName, definitionName, versionName, unsharedID string) (string, error) {
galleryResp, err := u.galleries.Get(ctx, u.resourceGroup, sigName, &armcomputev4.GalleriesClientGetOptions{})
if err != nil {
return "", fmt.Errorf("getting image gallery %s: %w", sigName, err)
}
if galleryResp.Properties == nil ||
galleryResp.Properties.SharingProfile == nil ||
galleryResp.Properties.SharingProfile.CommunityGalleryInfo == nil ||
galleryResp.Properties.SharingProfile.CommunityGalleryInfo.CommunityGalleryEnabled == nil ||
!*galleryResp.Properties.SharingProfile.CommunityGalleryInfo.CommunityGalleryEnabled {
u.log.Warnf("Image gallery %s in %s is not shared. Using private identifier", sigName, u.resourceGroup)
return unsharedID, nil
}
if galleryResp.Properties == nil ||
galleryResp.Properties.SharingProfile == nil ||
galleryResp.Properties.SharingProfile.CommunityGalleryInfo == nil ||
galleryResp.Properties.SharingProfile.CommunityGalleryInfo.PublicNames == nil ||
len(galleryResp.Properties.SharingProfile.CommunityGalleryInfo.PublicNames) < 1 ||
galleryResp.Properties.SharingProfile.CommunityGalleryInfo.PublicNames[0] == nil {
return "", fmt.Errorf("image gallery %s in %s is a community gallery but has no public names", sigName, u.resourceGroup)
}
communityGalleryName := *galleryResp.Properties.SharingProfile.CommunityGalleryInfo.PublicNames[0]
u.log.Debugf("Image gallery %s in %s is shared. Using community identifier in %s", sigName, u.resourceGroup, communityGalleryName)
communityVersionResp, err := u.communityVersions.Get(ctx, u.location, communityGalleryName,
definitionName, versionName,
&armcomputev4.CommunityGalleryImageVersionsClientGetOptions{},
)
if err != nil {
return "", fmt.Errorf("getting community image version %s/%s/%s: %w", communityGalleryName, definitionName, versionName, err)
}
if communityVersionResp.Identifier == nil || communityVersionResp.Identifier.UniqueID == nil {
return "", fmt.Errorf("community image version %s/%s/%s has no id", communityGalleryName, definitionName, versionName)
}
return *communityVersionResp.Identifier.UniqueID, nil
}
func uploadBlob(ctx context.Context, sasURL string, disk io.ReadSeeker, size int64, uploader sasBlobUploader) error {
uploadClient, err := uploader(sasURL)
if err != nil {
return fmt.Errorf("uploading blob: %w", err)
}
var offset int64
var chunksize int
chunk := make([]byte, pageSizeMax)
var readErr error
for offset < size {
chunksize, readErr = io.ReadAtLeast(disk, chunk, 1)
if readErr != nil {
return fmt.Errorf("reading from disk: %w", err)
}
if err := uploadChunk(ctx, uploadClient, bytes.NewReader(chunk[:chunksize]), offset, int64(chunksize)); err != nil {
return fmt.Errorf("uploading chunk: %w", err)
}
offset += int64(chunksize)
}
return nil
}
func uploadChunk(ctx context.Context, uploader azurePageblobAPI, chunk io.ReadSeeker, offset, chunksize int64) error {
_, err := uploader.UploadPages(ctx, &readSeekNopCloser{chunk}, blob.HTTPRange{
Offset: offset,
Count: chunksize,
}, nil)
return err
}
func imageOffer(version versionsapi.Version) string {
switch {
case version.Stream == "stable":
return "constellation"
case version.Stream == "debug" && version.Ref == "-":
return version.Version
}
return version.Ref + "-" + version.Stream
}
// imageVersion determines the semantic version string used inside a sig image.
// For releases, the actual semantic version of the image (without leading v) is used (major.minor.patch).
// Otherwise, the version is derived from the commit timestamp.
func imageVersion(version versionsapi.Version, timestamp time.Time) (string, error) {
switch {
case version.Stream == "stable":
fallthrough
case version.Stream == "debug" && version.Ref == "-":
return strings.TrimLeft(version.Version, "v"), nil
}
formattedTime := timestamp.Format(timestampFormat)
if len(formattedTime) != len(timestampFormat) {
return "", errors.New("invalid timestamp")
}
// <year>.<month><day>.<time>
return formattedTime[:4] + "." + formattedTime[4:8] + "." + formattedTime[8:], nil
}
type sasBlobUploader func(sasBlobURL string) (azurePageblobAPI, error)
type azureDiskAPI interface {
Get(ctx context.Context, resourceGroupName string, diskName string,
options *armcomputev4.DisksClientGetOptions,
) (armcomputev4.DisksClientGetResponse, error)
BeginCreateOrUpdate(ctx context.Context, resourceGroupName string, diskName string, disk armcomputev4.Disk,
options *armcomputev4.DisksClientBeginCreateOrUpdateOptions,
) (*runtime.Poller[armcomputev4.DisksClientCreateOrUpdateResponse], error)
BeginDelete(ctx context.Context, resourceGroupName string, diskName string,
options *armcomputev4.DisksClientBeginDeleteOptions,
) (*runtime.Poller[armcomputev4.DisksClientDeleteResponse], error)
BeginGrantAccess(ctx context.Context, resourceGroupName string, diskName string, grantAccessData armcomputev4.GrantAccessData,
options *armcomputev4.DisksClientBeginGrantAccessOptions,
) (*runtime.Poller[armcomputev4.DisksClientGrantAccessResponse], error)
BeginRevokeAccess(ctx context.Context, resourceGroupName string, diskName string,
options *armcomputev4.DisksClientBeginRevokeAccessOptions,
) (*runtime.Poller[armcomputev4.DisksClientRevokeAccessResponse], error)
}
type azureManagedImageAPI interface {
Get(ctx context.Context, resourceGroupName string, imageName string,
options *armcomputev4.ImagesClientGetOptions,
) (armcomputev4.ImagesClientGetResponse, error)
BeginCreateOrUpdate(ctx context.Context, resourceGroupName string,
imageName string, parameters armcomputev4.Image,
options *armcomputev4.ImagesClientBeginCreateOrUpdateOptions,
) (*runtime.Poller[armcomputev4.ImagesClientCreateOrUpdateResponse], error)
BeginDelete(ctx context.Context, resourceGroupName string, imageName string,
options *armcomputev4.ImagesClientBeginDeleteOptions,
) (*runtime.Poller[armcomputev4.ImagesClientDeleteResponse], error)
}
type azurePageblobAPI interface {
UploadPages(ctx context.Context, body io.ReadSeekCloser, contentRange blob.HTTPRange,
options *pageblob.UploadPagesOptions,
) (pageblob.UploadPagesResponse, error)
}
type azureGalleriesAPI interface {
Get(ctx context.Context, resourceGroupName string, galleryName string,
options *armcomputev4.GalleriesClientGetOptions,
) (armcomputev4.GalleriesClientGetResponse, error)
NewListPager(options *armcomputev4.GalleriesClientListOptions,
) *runtime.Pager[armcomputev4.GalleriesClientListResponse]
BeginCreateOrUpdate(ctx context.Context, resourceGroupName string,
galleryName string, gallery armcomputev4.Gallery,
options *armcomputev4.GalleriesClientBeginCreateOrUpdateOptions,
) (*runtime.Poller[armcomputev4.GalleriesClientCreateOrUpdateResponse], error)
}
type azureGalleriesImageAPI interface {
Get(ctx context.Context, resourceGroupName string, galleryName string,
galleryImageName string, options *armcomputev4.GalleryImagesClientGetOptions,
) (armcomputev4.GalleryImagesClientGetResponse, error)
BeginCreateOrUpdate(ctx context.Context, resourceGroupName string, galleryName string,
galleryImageName string, galleryImage armcomputev4.GalleryImage,
options *armcomputev4.GalleryImagesClientBeginCreateOrUpdateOptions,
) (*runtime.Poller[armcomputev4.GalleryImagesClientCreateOrUpdateResponse], error)
BeginDelete(ctx context.Context, resourceGroupName string, galleryName string, galleryImageName string,
options *armcomputev4.GalleryImagesClientBeginDeleteOptions,
) (*runtime.Poller[armcomputev4.GalleryImagesClientDeleteResponse], error)
}
type azureGalleriesImageVersionAPI interface {
Get(ctx context.Context, resourceGroupName string, galleryName string, galleryImageName string, galleryImageVersionName string,
options *armcomputev4.GalleryImageVersionsClientGetOptions,
) (armcomputev4.GalleryImageVersionsClientGetResponse, error)
NewListByGalleryImagePager(resourceGroupName string, galleryName string, galleryImageName string,
options *armcomputev4.GalleryImageVersionsClientListByGalleryImageOptions,
) *runtime.Pager[armcomputev4.GalleryImageVersionsClientListByGalleryImageResponse]
BeginCreateOrUpdate(ctx context.Context, resourceGroupName string, galleryName string, galleryImageName string,
galleryImageVersionName string, galleryImageVersion armcomputev4.GalleryImageVersion,
options *armcomputev4.GalleryImageVersionsClientBeginCreateOrUpdateOptions,
) (*runtime.Poller[armcomputev4.GalleryImageVersionsClientCreateOrUpdateResponse], error)
BeginDelete(ctx context.Context, resourceGroupName string, galleryName string, galleryImageName string,
galleryImageVersionName string, options *armcomputev4.GalleryImageVersionsClientBeginDeleteOptions,
) (*runtime.Poller[armcomputev4.GalleryImageVersionsClientDeleteResponse], error)
}
type azureCommunityGalleryImageVersionAPI interface {
Get(ctx context.Context, location string,
publicGalleryName, galleryImageName, galleryImageVersionName string,
options *armcomputev4.CommunityGalleryImageVersionsClientGetOptions,
) (armcomputev4.CommunityGalleryImageVersionsClientGetResponse, error)
}
const (
pollingFrequency = 10 * time.Second
// uploadAccessDuration is the time in seconds that
// sas tokens should be valid for (24 hours).
uploadAccessDuration = 86400 // 24 hours
pageSizeMax = 4194304 // 4MiB
pageSizeMin = 512 // 512 bytes
sigNameStable = "Constellation_CVM"
sigNameDebug = "Constellation_Debug_CVM"
sigNameDefault = "Constellation_Testing_CVM"
imageDefinitionPublisher = "edgelesssys"
imageDefinitionSKU = "constellation"
timestampFormat = "20060102150405"
)
var targetRegions = []*armcomputev4.TargetRegion{
{
Name: toPtr("northeurope"),
RegionalReplicaCount: toPtr[int32](1),
},
{
Name: toPtr("eastus"),
RegionalReplicaCount: toPtr[int32](1),
},
{
Name: toPtr("westeurope"),
RegionalReplicaCount: toPtr[int32](1),
},
{
Name: toPtr("westus"),
RegionalReplicaCount: toPtr[int32](1),
},
}
//go:generate stringer -type=DiskType -trimprefix=DiskType
// DiskType is the kind of disk created using the Azure API.
type DiskType uint32
// FromString converts a string into an DiskType.
func FromString(s string) DiskType {
switch strings.ToLower(s) {
case strings.ToLower(DiskTypeNormal.String()):
return DiskTypeNormal
case strings.ToLower(DiskTypeWithVMGS.String()):
return DiskTypeWithVMGS
default:
return DiskTypeUnknown
}
}
const (
// DiskTypeUnknown is default value for DiskType.
DiskTypeUnknown DiskType = iota
// DiskTypeNormal creates a normal Azure disk (single block device).
DiskTypeNormal
// DiskTypeWithVMGS creates a disk with VMGS (also called secure disk)
// that has an additional block device for the VMGS disk.
DiskTypeWithVMGS
)
func toPtr[T any](v T) *T {
return &v
}
type readSeekNopCloser struct {
io.ReadSeeker
}
func (n *readSeekNopCloser) Close() error {
return nil
}

View File

@ -0,0 +1,25 @@
// Code generated by "stringer -type=DiskType -trimprefix=DiskType"; DO NOT EDIT.
package azure
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[DiskTypeUnknown-0]
_ = x[DiskTypeNormal-1]
_ = x[DiskTypeWithVMGS-2]
}
const _DiskType_name = "UnknownNormalWithVMGS"
var _DiskType_index = [...]uint8{0, 7, 13, 21}
func (i DiskType) String() string {
if i >= DiskType(len(_DiskType_index)-1) {
return "DiskType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _DiskType_name[_DiskType_index[i]:_DiskType_index[i+1]]
}

View File

@ -0,0 +1,18 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "gcp",
srcs = ["gcpupload.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/gcp",
visibility = ["//:__subpackages__"],
deps = [
"//internal/logger",
"//internal/osimage",
"//internal/osimage/secureboot",
"//internal/versionsapi",
"@com_github_googleapis_gax_go_v2//:gax-go",
"@com_google_cloud_go_compute//apiv1",
"@com_google_cloud_go_compute//apiv1/computepb",
"@com_google_cloud_go_storage//:storage",
],
)

View File

@ -0,0 +1,289 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// package gcp implements uploading os images to gcp.
package gcp
import (
"context"
"encoding/base64"
"fmt"
"io"
"net/url"
"path"
"strings"
compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
"cloud.google.com/go/storage"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/secureboot"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
gaxv2 "github.com/googleapis/gax-go/v2"
)
// Uploader can upload and remove os images on GCP.
type Uploader struct {
project string
location string
bucketName string
image imagesAPI
bucket bucketAPI
log *logger.Logger
}
// New creates a new Uploader.
func New(ctx context.Context, project, location, bucketName string, log *logger.Logger) (*Uploader, error) {
image, err := compute.NewImagesRESTClient(ctx)
if err != nil {
return nil, err
}
storage, err := storage.NewClient(ctx)
if err != nil {
return nil, err
}
bucket := storage.Bucket(bucketName)
return &Uploader{
project: project,
location: location,
bucketName: bucketName,
image: image,
bucket: bucket,
log: log,
}, nil
}
// Upload uploads an OS image to GCP.
func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) {
imageName := u.imageName(req.Version, req.Variant)
blobName := imageName + ".tar.gz"
if err := u.ensureBucket(ctx); err != nil {
return nil, fmt.Errorf("setup: ensuring bucket exists: %w", err)
}
if err := u.ensureImageDeleted(ctx, imageName); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no image using the same name exists: %w", err)
}
if err := u.ensureBlobDeleted(ctx, blobName); err != nil {
return nil, fmt.Errorf("pre-cleaning: ensuring no blob using the same name exists: %w", err)
}
if err := u.uploadBlob(ctx, blobName, req.Image); err != nil {
return nil, fmt.Errorf("uploading blob: %w", err)
}
defer func() {
// cleanup temporary blob
if err := u.ensureBlobDeleted(ctx, blobName); err != nil {
u.log.Errorf("post-cleaning: deleting blob: %v", err)
}
}()
imageRef, err := u.createImage(ctx, req.Version, imageName, blobName, req.SBDatabase)
if err != nil {
return nil, fmt.Errorf("creating image: %w", err)
}
return map[string]string{
req.Variant: imageRef,
}, nil
}
func (u *Uploader) ensureBucket(ctx context.Context) error {
_, err := u.bucket.Attrs(ctx)
if err == nil {
u.log.Debugf("Bucket %s exists", u.bucketName)
return nil
}
if err != storage.ErrBucketNotExist {
return err
}
u.log.Debugf("Creating bucket %s", u.bucketName)
return u.bucket.Create(ctx, u.project, &storage.BucketAttrs{
PublicAccessPrevention: storage.PublicAccessPreventionEnforced,
Location: u.location,
})
}
func (u *Uploader) uploadBlob(ctx context.Context, blobName string, img io.Reader) error {
u.log.Debugf("Uploading os image as %s", blobName)
writer := u.bucket.Object(blobName).NewWriter(ctx)
_, err := io.Copy(writer, img)
if err != nil {
return err
}
return writer.Close()
}
func (u *Uploader) ensureBlobDeleted(ctx context.Context, blobName string) error {
_, err := u.bucket.Object(blobName).Attrs(ctx)
if err == storage.ErrObjectNotExist {
u.log.Debugf("Blob %s in %s doesn't exist. Nothing to clean up.", blobName, u.bucketName)
return nil
}
if err != nil {
return err
}
u.log.Debugf("Deleting blob %s", blobName)
return u.bucket.Object(blobName).Delete(ctx)
}
func (u *Uploader) createImage(ctx context.Context, version versionsapi.Version, imageName, blobName string, sbDatabase secureboot.Database) (string, error) {
u.log.Debugf("Creating image %s", imageName)
blobURL := u.blobURL(blobName)
family := u.imageFamily(version)
req := computepb.InsertImageRequest{
ImageResource: &computepb.Image{
Name: &imageName,
RawDisk: &computepb.RawDisk{
ContainerType: toPtr("TAR"),
Source: &blobURL,
},
Family: &family,
Architecture: toPtr("X86_64"),
GuestOsFeatures: []*computepb.GuestOsFeature{
{Type: toPtr("GVNIC")},
{Type: toPtr("SEV_CAPABLE")},
{Type: toPtr("VIRTIO_SCSI_MULTIQUEUE")},
{Type: toPtr("UEFI_COMPATIBLE")},
},
ShieldedInstanceInitialState: &computepb.InitialStateConfig{
Pk: pk(&sbDatabase),
Keks: keks(&sbDatabase),
Dbs: dbs(&sbDatabase),
},
},
Project: u.project,
}
op, err := u.image.Insert(ctx, &req)
if err != nil {
return "", fmt.Errorf("creating image: %w", err)
}
if err := op.Wait(ctx); err != nil {
return "", fmt.Errorf("waiting for image to be created: %w", err)
}
policy := &computepb.Policy{
Bindings: []*computepb.Binding{
{
Role: toPtr("roles/compute.imageUser"),
Members: []string{"allAuthenticatedUsers"},
},
},
}
if _, err = u.image.SetIamPolicy(ctx, &computepb.SetIamPolicyImageRequest{
Resource: imageName,
Project: u.project,
GlobalSetPolicyRequestResource: &computepb.GlobalSetPolicyRequest{
Policy: policy,
},
}); err != nil {
return "", fmt.Errorf("setting iam policy: %w", err)
}
image, err := u.image.Get(ctx, &computepb.GetImageRequest{
Image: imageName,
Project: u.project,
})
if err != nil {
return "", fmt.Errorf("created image doesn't exist: %w", err)
}
return strings.TrimPrefix(image.GetSelfLink(), "https://www.googleapis.com/compute/v1/"), nil
}
func (u *Uploader) ensureImageDeleted(ctx context.Context, imageName string) error {
_, err := u.image.Get(ctx, &computepb.GetImageRequest{
Image: imageName,
Project: u.project,
})
if err != nil {
u.log.Debugf("Image %s doesn't exist. Nothing to clean up.", imageName)
return nil
}
u.log.Debugf("Deleting image %s", imageName)
op, err := u.image.Delete(ctx, &computepb.DeleteImageRequest{
Image: imageName,
Project: u.project,
})
if err != nil {
return err
}
return op.Wait(ctx)
}
func (u *Uploader) blobURL(blobName string) string {
return (&url.URL{
Scheme: "https",
Host: "storage.googleapis.com",
Path: path.Join(u.bucketName, blobName),
}).String()
}
func (u *Uploader) imageName(version versionsapi.Version, variant string) string {
return strings.ReplaceAll(version.Version, ".", "-") + "-" + variant
}
func (u *Uploader) imageFamily(version versionsapi.Version) string {
if version.Stream == "stable" {
return "constellation"
}
truncatedRef := version.Ref
if len(version.Ref) > 45 {
truncatedRef = version.Ref[:45]
}
return "constellation-" + truncatedRef
}
func pk(sbDatabase *secureboot.Database) *computepb.FileContentBuffer {
encoded := base64.StdEncoding.EncodeToString(sbDatabase.PK)
return &computepb.FileContentBuffer{
Content: toPtr(encoded),
FileType: toPtr("X509"),
}
}
func keks(sbDatabase *secureboot.Database) []*computepb.FileContentBuffer {
keks := make([]*computepb.FileContentBuffer, 0, len(sbDatabase.Keks))
for _, kek := range sbDatabase.Keks {
encoded := base64.StdEncoding.EncodeToString(kek)
keks = append(keks, &computepb.FileContentBuffer{
Content: toPtr(encoded),
FileType: toPtr("X509"),
})
}
return keks
}
func dbs(sbDatabase *secureboot.Database) []*computepb.FileContentBuffer {
dbs := make([]*computepb.FileContentBuffer, 0, len(sbDatabase.DBs))
for _, db := range sbDatabase.DBs {
encoded := base64.StdEncoding.EncodeToString(db)
dbs = append(dbs, &computepb.FileContentBuffer{
Content: toPtr(encoded),
FileType: toPtr("X509"),
})
}
return dbs
}
type imagesAPI interface {
Get(ctx context.Context, req *computepb.GetImageRequest, opts ...gaxv2.CallOption,
) (*computepb.Image, error)
Insert(ctx context.Context, req *computepb.InsertImageRequest, opts ...gaxv2.CallOption,
) (*compute.Operation, error)
SetIamPolicy(ctx context.Context, req *computepb.SetIamPolicyImageRequest, opts ...gaxv2.CallOption,
) (*computepb.Policy, error)
Delete(ctx context.Context, req *computepb.DeleteImageRequest, opts ...gaxv2.CallOption,
) (*compute.Operation, error)
io.Closer
}
type bucketAPI interface {
Attrs(ctx context.Context) (attrs *storage.BucketAttrs, err error)
Create(ctx context.Context, projectID string, attrs *storage.BucketAttrs) (err error)
Object(name string) *storage.ObjectHandle
}
func toPtr[T any](v T) *T {
return &v
}

View File

@ -0,0 +1,12 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "nop",
srcs = ["nop.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/nop",
visibility = ["//:__subpackages__"],
deps = [
"//internal/logger",
"//internal/osimage",
],
)

View File

@ -0,0 +1,31 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// package nop implements a no-op for CSPs that don't require custom image upload functionality.
package nop
import (
"context"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
)
// Uploader is a no-op uploader.
type Uploader struct {
log *logger.Logger
}
// New creates a new Uploader.
func New(log *logger.Logger) *Uploader {
return &Uploader{log: log}
}
// Upload pretends to upload images to a csp.
func (u *Uploader) Upload(_ context.Context, req *osimage.UploadRequest) (map[string]string, error) {
u.log.Debugf("Skipping image upload of %s since this CSP does not require images to be uploaded in advance.", req.Version.ShortPath())
return nil, nil
}

View File

@ -0,0 +1,29 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// package osimage is used to handle osimages in the CI (uploading and maintenance).
package osimage
import (
"io"
"time"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/osimage/secureboot"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
)
// UploadRequest is a request to upload an os image.
type UploadRequest struct {
Provider cloudprovider.Provider
Version versionsapi.Version
Variant string
SBDatabase secureboot.Database
UEFIVarStore secureboot.UEFIVarStore
Size int64
Timestamp time.Time
Image io.ReadSeeker
}

View File

@ -0,0 +1,25 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "secureboot",
srcs = [
"secureboot.go",
"zlibdict.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/secureboot",
visibility = ["//:__subpackages__"],
deps = ["@com_github_spf13_afero//:afero"],
)
go_test(
name = "secureboot_test",
srcs = ["secureboot_test.go"],
embed = [":secureboot"],
deps = [
"@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak",
],
)

View File

@ -0,0 +1,285 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// package secureboot holds secure boot configuration for image uploads.
package secureboot
import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/binary"
"fmt"
"hash/crc32"
"io"
"os"
"github.com/spf13/afero"
)
// Database holds the secure boot database that cloud providers should
// use when enabling secure boot for a Constellation OS image.
type Database struct {
// PK is the platform key.
PK []byte
// Keks are trusted key-exchange-keys
Keks [][]byte
// DBs are entries of the signature database.
DBs [][]byte
}
// DatabaseFromFiles creates the secure boot database from individual files.
func DatabaseFromFiles(fs afero.Fs, pk string, keks []string, dbs []string) (Database, error) {
rawPK, err := afero.ReadFile(fs, pk)
if err != nil {
return Database{}, fmt.Errorf("loading PK %s: %w", pk, err)
}
rawKEKs := make([][]byte, len(keks))
for i, kek := range keks {
rawKEK, err := afero.ReadFile(fs, kek)
if err != nil {
return Database{}, fmt.Errorf("loading KEK %s: %w", kek, err)
}
rawKEKs[i] = rawKEK
}
rawDBs := make([][]byte, len(dbs))
for i, db := range dbs {
rawDB, err := afero.ReadFile(fs, db)
if err != nil {
return Database{}, fmt.Errorf("loading DB %s: %w", db, err)
}
rawDBs[i] = rawDB
}
return Database{
PK: rawPK,
Keks: rawKEKs,
DBs: rawDBs,
}, nil
}
// UEFIVarStore is a UEFI variable store.
// It is a collection of UEFIVar structs.
// This is an abstract var store that can convert to a concrete var store
// for a specific CSP.
type UEFIVarStore []UEFIVar
// VarStoreFromFiles creates the UEFI variable store
// from "EFI Signature List" (esl) files.
func VarStoreFromFiles(fs afero.Fs, pk, kek, db, dbx string) (UEFIVarStore, error) {
vars := UEFIVarStore{}
pkF, err := fs.OpenFile(pk, os.O_RDONLY, os.ModePerm)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("opening PK ESL %s: %w", pk, err)
}
defer pkF.Close()
pkVar, err := ReadVar(pkF, "PK", globalEFIGUID)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("reading PK ESL %s: %w", pk, err)
}
vars = append(vars, pkVar)
kekF, err := fs.OpenFile(kek, os.O_RDONLY, os.ModePerm)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("opening KEK ESL %s: %w", kek, err)
}
defer kekF.Close()
kekVar, err := ReadVar(kekF, "KEK", globalEFIGUID)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("reading KEK ESL %s: %w", kek, err)
}
vars = append(vars, kekVar)
dbF, err := fs.OpenFile(db, os.O_RDONLY, os.ModePerm)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("opening DB ESL %s: %w", db, err)
}
defer dbF.Close()
dbVar, err := ReadVar(dbF, "db", secureDatabaseGUID)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("reading DB ESL %s: %w", db, err)
}
vars = append(vars, dbVar)
if len(dbx) == 0 {
return vars, nil
}
dbxF, err := fs.OpenFile(dbx, os.O_RDONLY, os.ModePerm)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("opening DBX ESL %s: %w", dbx, err)
}
defer dbxF.Close()
dbxVar, err := ReadVar(dbxF, "dbx", secureDatabaseGUID)
if err != nil {
return UEFIVarStore{}, fmt.Errorf("reading DBX ESL %s: %w", dbx, err)
}
vars = append(vars, dbxVar)
return vars, nil
}
// ToAWS converts the UEFI variable store to the AWS UEFI vars v0 format.
// The format is documented here:
// https://github.com/awslabs/python-uefivars
// It is structured as follows:
// Header:
// - 4 bytes: magic number
// - 4 bytes: crc32 of the rest of the file
// - 4 bytes: version number
//
// Body is zlib compressed stream of:
// 8 bytes number of entries
// for each entry:
// - name (variable length field, utf8)
// - data (variable length field)
// - guid (16 bytes)
// - attr (int32 in little endian)
// OPTIONAL (if attr has EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set):
// - timestamp (16 bytes)
// - digest (variable length field).
func (s UEFIVarStore) ToAWS() (string, error) {
payload := bytes.Buffer{}
// Write the number of entries.
if err := binary.Write(&payload, binary.LittleEndian, uint64(len(s))); err != nil {
return "", fmt.Errorf("writing number of entries: %w", err)
}
// Write the entries.
for _, entry := range s {
rawEntry, err := entry.AWSEntry()
if err != nil {
return "", fmt.Errorf("serializing entry: %w", err)
}
if _, err := payload.Write(rawEntry); err != nil {
return "", fmt.Errorf("writing entry: %w", err)
}
}
// Compress the payload.
compressed := bytes.Buffer{}
zlibW, err := zlib.NewWriterLevelDict(&compressed, zlib.BestCompression, zlibDict)
if err != nil {
return "", fmt.Errorf("creating compressor: %w", err)
}
if _, err := zlibW.Write(payload.Bytes()); err != nil {
return "", fmt.Errorf("compressing payload: %w", err)
}
if err := zlibW.Close(); err != nil {
return "", fmt.Errorf("closing compressor: %w", err)
}
compressedData := compressed.Bytes()
// Calculate the CRC32 (Castagnoli) of the version + compressed payload.
crcData := append(awsVersion, compressedData...)
crc := crc32.Checksum(crcData, crc32.MakeTable(crc32.Castagnoli))
out := bytes.Buffer{}
// Write the header.
if _, err := out.Write(awsMagic); err != nil {
return "", fmt.Errorf("writing magic: %w", err)
}
if err := binary.Write(&out, binary.LittleEndian, crc); err != nil {
return "", fmt.Errorf("writing crc: %w", err)
}
// Write the version + compressed payload.
if _, err := out.Write(crcData); err != nil {
return "", fmt.Errorf("writing compressed payload: %w", err)
}
return base64.StdEncoding.EncodeToString(out.Bytes()), nil
}
// UEFIVar is a UEFI variable.
type UEFIVar struct {
Name string
Data []byte
GUID []byte
Attr uint32
Timestamp []byte
Digest []byte
}
// ReadVar reads a UEFI variable from an ESL file.
func ReadVar(reader io.Reader, name string, guid []byte) (UEFIVar, error) {
attr := uint32(
EFIVariableNonVolatile |
EFIVariableBootServiceAccess |
EFIVariableRuntimeAccess |
EFIVariableTimeBasedAuthenticatedWriteAccess,
)
data, err := io.ReadAll(reader)
if err != nil {
return UEFIVar{}, err
}
return UEFIVar{
Name: name,
Data: data,
GUID: guid,
Attr: attr,
Timestamp: []byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
Digest: []byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
}, nil
}
// AWSEntry returns the AWS format entry for the UEFI variable.
func (v UEFIVar) AWSEntry() ([]byte, error) {
var buf bytes.Buffer
if err := appendVariableLengthField(&buf, []byte(v.Name)); err != nil {
return nil, err
}
if err := appendVariableLengthField(&buf, v.Data); err != nil {
return nil, err
}
if _, err := buf.Write(v.GUID); err != nil {
return nil, err
}
if err := appendAttr(&buf, v.Attr); err != nil {
return nil, err
}
if v.Attr&EFIVariableTimeBasedAuthenticatedWriteAccess == 0 {
return buf.Bytes(), nil
}
if _, err := buf.Write(v.Timestamp); err != nil {
return nil, err
}
if err := appendVariableLengthField(&buf, v.Digest); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func appendVariableLengthField(w io.Writer, data []byte) error {
// variable length is encoded as unsigned, 64 bit little endian
// followed by the data
if err := binary.Write(w, binary.LittleEndian, uint64(len(data))); err != nil {
return err
}
_, err := w.Write(data)
return err
}
func appendAttr(w io.Writer, attr uint32) error {
return binary.Write(w, binary.LittleEndian, attr)
}
// EFI constants.
const (
EFIVariableNonVolatile = 0x00000001
EFIVariableBootServiceAccess = 0x00000002
EFIVariableRuntimeAccess = 0x00000004
EFIVariableTimeBasedAuthenticatedWriteAccess = 0x00000020
)
var (
awsMagic = []byte("AMZNUEFI")
awsVersion = []byte{0, 0, 0, 0}
globalEFIGUID = []byte{
0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11,
0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c,
}
secureDatabaseGUID = []byte{
0xcb, 0xb2, 0x19, 0xd7, 0x3a, 0x3d, 0x96, 0x45,
0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f,
}
)

View File

@ -0,0 +1,182 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package secureboot
import (
"encoding/base64"
"testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestDatabaseFromFile(t *testing.T) {
testCases := map[string]struct {
pk string
keks []string
dbs []string
wantErr bool
}{
"found": {
pk: "pki/pk.cer",
keks: []string{"pki/kek.cer"},
dbs: []string{"pki/db.cer"},
},
"pk not found": {
pk: "pki/missing",
keks: []string{"pki/kek.cer"},
dbs: []string{"pki/db.cer"},
wantErr: true,
},
"kek not found": {
pk: "pki/pk.cer",
keks: []string{"pki/missing"},
dbs: []string{"pki/db.cer"},
wantErr: true,
},
"db not found": {
pk: "pki/pk.cer",
keks: []string{"pki/kek.cer"},
dbs: []string{"pki/missing"},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
fs := afero.NewMemMapFs()
require.NoError(afero.WriteFile(fs, "pki/pk.cer", []byte("pk"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/kek.cer", []byte("kek"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/db.cer", []byte("db"), 0o644))
db, err := DatabaseFromFiles(fs, tc.pk, tc.keks, tc.dbs)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
assert.Len(db.Keks, 1)
assert.Len(db.DBs, 1)
assert.Equal("pk", string(db.PK))
assert.Equal("kek", string(db.Keks[0]))
assert.Equal("db", string(db.DBs[0]))
})
}
}
func TestVarsStoreFromFiles(t *testing.T) {
testCases := map[string]struct {
pk string
kek string
db string
dbx string
wantErr bool
}{
"found": {
pk: "pki/pk.esl",
kek: "pki/kek.esl",
db: "pki/db.esl",
dbx: "pki/dbx.esl",
},
"no dbx": {
pk: "pki/pk.esl",
kek: "pki/kek.esl",
db: "pki/db.esl",
},
"pk not found": {
pk: "pki/missing",
kek: "pki/kek.esl",
db: "pki/db.esl",
wantErr: true,
},
"kek not found": {
pk: "pki/pk.esl",
kek: "pki/missing",
db: "pki/db.esl",
wantErr: true,
},
"db not found": {
pk: "pki/pk.esl",
kek: "pki/kek.esl",
db: "pki/missing",
wantErr: true,
},
"dbx not found": {
pk: "pki/pk.esl",
kek: "pki/kek.esl",
db: "pki/db.esl",
dbx: "pki/missing",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
fs := afero.NewMemMapFs()
require.NoError(afero.WriteFile(fs, "pki/pk.esl", []byte("pk"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/kek.esl", []byte("kek"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/db.esl", []byte("db"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/dbx.esl", []byte("dbx"), 0o644))
store, err := VarStoreFromFiles(fs, tc.pk, tc.kek, tc.db, tc.dbx)
if tc.wantErr {
assert.Error(err)
return
}
require.NoError(err)
if tc.dbx == "" {
assert.Len(store, 3)
} else {
assert.Len(store, 4)
assert.Equal("dbx", store[3].Name)
assert.Equal("dbx", string(store[3].Data))
assert.Equal(secureDatabaseGUID, store[3].GUID)
}
assert.Equal("PK", store[0].Name)
assert.Equal("pk", string(store[0].Data))
assert.Equal(globalEFIGUID, store[0].GUID)
assert.Equal("KEK", store[1].Name)
assert.Equal("kek", string(store[1].Data))
assert.Equal(globalEFIGUID, store[1].GUID)
assert.Equal("db", store[2].Name)
assert.Equal("db", string(store[2].Data))
assert.Equal(secureDatabaseGUID, store[2].GUID)
})
}
}
func TestToAWS(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
fs := afero.NewMemMapFs()
require.NoError(afero.WriteFile(fs, "pki/pk.esl", []byte("pk"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/kek.esl", []byte("kek"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/db.esl", []byte("db"), 0o644))
require.NoError(afero.WriteFile(fs, "pki/dbx.esl", []byte("dbx"), 0o644))
store, err := VarStoreFromFiles(fs, "pki/pk.esl", "pki/kek.esl", "pki/db.esl", "pki/dbx.esl")
require.NoError(err)
awsData, err := store.ToAWS()
require.NoError(err)
out, err := base64.StdEncoding.DecodeString(awsData)
assert.NoError(err)
assert.Equal([]byte{
0x41, 0x4d, 0x5a, 0x4e, 0x55, 0x45, 0x46, 0x49, 0x5d, 0x52, 0x8c, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x78, 0xf9, 0x6b, 0xb7, 0xd9, 0xf7, 0x62, 0x81, 0xda, 0xc4, 0x04, 0xa5, 0x03, 0xbc, 0x61, 0xac,
0x82, 0x6c, 0x74, 0x5b, 0xd5, 0xb1, 0xb8, 0x50, 0x81, 0xc8, 0x25, 0xbe, 0xb0, 0x69, 0x3d, 0x6f,
0x57, 0x6f, 0x18, 0x33, 0x3b, 0x95, 0xaa, 0x36, 0xc0, 0xdc, 0x9d, 0x92, 0x84, 0x60, 0xa1, 0xb7,
0xcc, 0xa9, 0xe1, 0x83, 0x94, 0xa4, 0x0a, 0x24, 0x26, 0x35, 0x6d, 0x00, 0x04, 0x00, 0x00, 0xff,
0xff, 0xd6, 0x50, 0x28, 0x9b,
}, out)
}

View File

@ -0,0 +1,961 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package secureboot
// zlibDict is the zlib dictionary used for compressing the aws efivars.
// See also https://github.com/awslabs/python-uefivars/blob/9679002a4392d8e7831d2dbda3fab41ccc5c6b8c/pyuefivars/aws_v0.py.
var zlibDict = []byte{
0x37, 0xa4, 0x30, 0xec, 0x12, 0x44, 0xfc, 0x0a, 0x1d, 0x10, 0x28, 0x9d,
0x00, 0x00, 0x87, 0xae, 0xe3, 0x9e, 0xd0, 0x73, 0x2d, 0x2b, 0x4b, 0xca,
0x1e, 0xac, 0x1c, 0xfb, 0x79, 0xc8, 0x7c, 0x05, 0xfb, 0xf4, 0xf4, 0xf5,
0xf7, 0x01, 0x43, 0x05, 0x28, 0x00, 0x06, 0x03, 0x84, 0xe2, 0xd1, 0x60,
0x28, 0x4b, 0x06, 0x77, 0x00, 0x04, 0x80, 0xc0, 0x04, 0x0a, 0x1c, 0x0e,
0x06, 0x10, 0x08, 0x0a, 0x09, 0x05, 0x07, 0x34, 0x07, 0x04, 0x0a, 0x0f,
0x03, 0x07, 0x09, 0x0c, 0x07, 0x04, 0x05, 0x06, 0x08, 0x85, 0xc1, 0x60,
0x61, 0x68, 0x48, 0x20, 0x09, 0x07, 0x03, 0x71, 0xa1, 0x3c, 0xd5, 0x87,
0x98, 0x01, 0xd8, 0x00, 0x24, 0x51, 0x0f, 0x22, 0x11, 0x4c, 0x51, 0x0c,
0x02, 0x01, 0x00, 0x00, 0x82, 0x00, 0x21, 0xc8, 0xb8, 0x60, 0x68, 0x68,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00,
0x00, 0x00, 0x02, 0x1f, 0x03, 0x12, 0x0a, 0x00, 0x00, 0x00, 0xff, 0xff,
0x00, 0xb0, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f,
0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02,
0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01,
0x05, 0x00, 0x01, 0x03, 0x0d, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x87, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11,
0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00,
0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01,
0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x05,
0x00, 0x0f, 0x01, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f,
0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02,
0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01,
0x05, 0x00, 0xef, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93,
0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00,
0x6f, 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00,
0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41,
0x01, 0x05, 0x00, 0x2b, 0x29, 0x58, 0x9e, 0x68, 0x7c, 0x7d, 0x49, 0xa0,
0xce, 0x65, 0x00, 0xfd, 0x9f, 0x1b, 0x95, 0x2c, 0xaf, 0x2c, 0x64, 0xfe,
0xff, 0xff, 0xff, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x4d, 0x69,
0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x57, 0x69, 0x6e, 0x64,
0x6f, 0x77, 0x73, 0x20, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x20, 0x50, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x31, 0x30,
0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
0x0d, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x61, 0xdf,
0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03,
0x2b, 0x8c, 0x4c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00,
0x65, 0x6e, 0x67, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00,
0x1a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b,
0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c,
0x50, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00,
0x72, 0x00, 0x6d, 0x00, 0x4c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00,
0x00, 0x00, 0x65, 0x6e, 0x00, 0xff, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00,
0x07, 0x00, 0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0xd6,
0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f,
0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00,
0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00,
0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00,
0x65, 0x00, 0x72, 0x00, 0x28, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad,
0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00,
0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00,
0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00,
0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x28, 0x00, 0x00, 0x00, 0x05, 0x00,
0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44,
0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00,
0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00,
0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00,
0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x28, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45,
0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00,
0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00,
0x74, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00,
0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00,
0x00, 0x21, 0xe5, 0x7f, 0x93, 0xae, 0x95, 0x1a, 0x4d, 0x89, 0x29, 0x48,
0xbc, 0xd9, 0x0a, 0xd3, 0x1a, 0x35, 0x00, 0x32, 0x00, 0x35, 0x00, 0x34,
0x00, 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34,
0x00, 0x35, 0x00, 0x36, 0x00, 0xe8, 0x7f, 0xb3, 0x04, 0xae, 0xf6, 0x0b,
0x48, 0xbd, 0xd5, 0x37, 0xd9, 0x8c, 0x5e, 0x89, 0xaa, 0x56, 0x00, 0x61,
0x00, 0x72, 0x00, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x72,
0x00, 0x46, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x67, 0x00, 0x00, 0x00, 0xff,
0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x88, 0x9c, 0xd0, 0xf7, 0xb6,
0xc4, 0x7a, 0xd5, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x65, 0x00, 0x0e, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6e, 0xe5, 0xbe, 0xd9, 0xdc, 0x75,
0xd9, 0x49, 0xb4, 0xd7, 0xb5, 0x34, 0x21, 0x0f, 0x63, 0x7a, 0x63, 0x00,
0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x64, 0x00, 0x62, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x23, 0x00,
0x00, 0x04, 0x01, 0x2a, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xbc,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x6f, 0xbd, 0x26, 0x17, 0x0a, 0xae, 0xeb, 0x11, 0xaf, 0xcc, 0xb2,
0x6f, 0x04, 0x02, 0x45, 0xc6, 0x02, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44,
0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00,
0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00,
0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00,
0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x12, 0x34, 0x56, 0xff,
0xff, 0xaa, 0x55, 0x50, 0x00, 0x58, 0x00, 0x45, 0x00, 0x76, 0x00, 0x43,
0x00, 0x75, 0x00, 0x72, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74,
0x00, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00,
0x77, 0x00, 0x73, 0x00, 0x11, 0xd4, 0x9a, 0x46, 0x00, 0x90, 0x27, 0x3f,
0xc1, 0x4d, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4,
0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b,
0x8c, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74,
0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00,
0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c,
0x00, 0xd0, 0x41, 0x01, 0x05, 0x00, 0x02, 0xbd, 0x9a, 0xfa, 0x77, 0x59,
0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x06,
0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3d, 0x03, 0x0e, 0x00,
0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93,
0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00,
0x6f, 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00,
0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41,
0x01, 0x05, 0x00, 0x11, 0xd4, 0x9a, 0x38, 0x00, 0x90, 0x27, 0x3f, 0xc1,
0x4d, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77, 0x59,
0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x00,
0x50, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, 0x79, 0x00,
0x2d, 0x88, 0x11, 0xd3, 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d,
0x00, 0xaf, 0xaf, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x6f, 0x22, 0xec,
0xea, 0xa3, 0xc9, 0x7a, 0x47, 0xa8, 0x26, 0xdd, 0xc7, 0x16, 0xcd, 0xc0,
0xe3, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x6f, 0x22, 0xec, 0xea,
0xa3, 0xc9, 0x7a, 0x47, 0xa8, 0x26, 0xdd, 0xc7, 0x16, 0xcd, 0xc0, 0xe3,
0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04,
0x00, 0x05, 0x00, 0x06, 0x00, 0x11, 0xd2, 0x8e, 0x39, 0x00, 0xa0, 0xc9,
0x69, 0x72, 0x3b, 0xbd, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d,
0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x03, 0x18, 0x04, 0x00,
0x7f, 0xff, 0x04, 0x00, 0x4e, 0xac, 0x08, 0x81, 0x11, 0x9f, 0x59, 0x4d,
0x85, 0x0e, 0xe2, 0x1a, 0x52, 0x2c, 0x59, 0xb2, 0x00, 0x20, 0x00, 0x51,
0x00, 0x4d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x0e,
0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca,
0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x45,
0x00, 0x72, 0x00, 0x72, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00,
0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0,
0x41, 0x01, 0x05, 0x00, 0x30, 0x00, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77,
0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b,
0x80, 0xb4, 0xd9, 0x69, 0x31, 0xbf, 0x0d, 0x02, 0xfd, 0x91, 0xa6, 0x1e,
0x19, 0xd1, 0x4f, 0x1d, 0xa4, 0x52, 0xe6, 0x6d, 0xb2, 0x40, 0x8c, 0xa8,
0x60, 0x4d, 0x41, 0x1f, 0x92, 0x65, 0x9f, 0x0a, 0xbd, 0x9a, 0xfa, 0x77,
0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0x07, 0x00, 0xaa, 0x55, 0x3d,
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00,
0x00, 0x58, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x17, 0x00,
0x00, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00,
0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0,
0x98, 0x6b, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f,
0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c,
0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06,
0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00,
0x00, 0x00, 0x00, 0x7f, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03,
0x13, 0x21, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20,
0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x4b, 0x45, 0x4b, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x31, 0x30,
0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x61,
0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98,
0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e,
0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00,
0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c,
0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d,
0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x4b, 0x00, 0x65, 0x00,
0x72, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x5f, 0x00, 0x00, 0x37,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x45, 0x00, 0x46,
0x00, 0x49, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65,
0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x53,
0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04,
0x07, 0x14, 0x00, 0xc9, 0xbd, 0xb8, 0x7c, 0xeb, 0xf8, 0x34, 0x4f, 0xaa,
0xea, 0x3e, 0x0e, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93,
0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x4b, 0x00,
0x65, 0x00, 0x79, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x51, 0xd7, 0x97, 0x9f, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00,
0x00, 0x76, 0x00, 0x34, 0x00, 0x20, 0x00, 0x28, 0x00, 0x4d, 0x00, 0x41,
0x00, 0x43, 0x00, 0x3a, 0x00, 0x35, 0x00, 0x32, 0x00, 0x35, 0x00, 0x34,
0x00, 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34,
0x00, 0x35, 0x00, 0x36, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c,
0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06,
0x00, 0x00, 0x02, 0x03, 0x0b, 0x25, 0x00, 0x52, 0x54, 0x00, 0x12, 0x34,
0x56, 0x00, 0x00, 0x53, 0x00, 0x69, 0x00, 0x53, 0x00, 0x74, 0x00, 0x61,
0x00, 0x74, 0x00, 0x75, 0x00, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xbd,
0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7,
0x8f, 0x78, 0x4b, 0x4b, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x65,
0x00, 0x6c, 0x00, 0x5f, 0x00, 0x50, 0x00, 0x4b, 0x00, 0x00, 0x00, 0xa1,
0x59, 0xc0, 0xa5, 0xe4, 0x94, 0xa7, 0x4a, 0x87, 0xb5, 0xab, 0x15, 0x5c,
0x2b, 0xf0, 0x72, 0xd0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb4,
0x03, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa,
0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x30, 0x82, 0x03, 0xa0, 0x30,
0x82, 0x02, 0x88, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0xfe,
0xf5, 0x88, 0xe8, 0xf3, 0x96, 0xc0, 0xf1, 0x30, 0x0d, 0x06, 0xb9, 0x00,
0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d,
0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00,
0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41,
0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f,
0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00,
0x7f, 0x7a, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f,
0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c,
0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06,
0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00,
0x00, 0x00, 0x00, 0x7f, 0x37, 0x32, 0x31, 0x32, 0x32, 0x34, 0x35, 0x5a,
0x17, 0x0d, 0x32, 0x36, 0x30, 0x36, 0x32, 0x37, 0x32, 0x31, 0x33, 0x32,
0x34, 0x35, 0x5a, 0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06,
0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e,
0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04,
0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e,
0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x00, 0x76, 0x00, 0x36, 0x00,
0x20, 0x00, 0x28, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x43, 0x00, 0x3a, 0x00,
0x35, 0x00, 0x32, 0x00, 0x35, 0x00, 0x34, 0x00, 0x30, 0x00, 0x30, 0x00,
0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00,
0x29, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x02, 0x03, 0x0b,
0x25, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, 0x4d,
0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x52, 0x6f, 0x6f,
0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20,
0x32, 0x30, 0x31, 0x30, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x31, 0x30,
0x31, 0x39, 0x31, 0x38, 0x34, 0x31, 0x34, 0x32, 0x5a, 0x17, 0x0d, 0x32,
0x36, 0x31, 0x30, 0x31, 0x39, 0x31, 0x38, 0x35, 0x31, 0x34, 0x32, 0x5a,
0x30, 0x81, 0x84, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x74, 0x30,
0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0x14, 0xfc, 0x7c, 0x71, 0x51,
0xa5, 0x79, 0xc2, 0x6e, 0xb2, 0xef, 0x39, 0x3e, 0xbc, 0x3c, 0x52, 0x0f,
0x6e, 0x2b, 0x3f, 0x10, 0x13, 0x73, 0xfe, 0xa8, 0x68, 0xd0, 0x48, 0xa6,
0x34, 0x4d, 0x8a, 0x96, 0x05, 0x26, 0xee, 0x31, 0x46, 0x90, 0x61, 0x79,
0xd6, 0x7f, 0xff, 0x04, 0x00, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00,
0xe0, 0x98, 0x03, 0x2b, 0xc3, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b,
0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c,
0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00,
0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41,
0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x14, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa,
0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f,
0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xaa, 0x55, 0x3f, 0x00, 0x07,
0x00, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0xec, 0x76,
0xc0, 0x28, 0x70, 0x99, 0x43, 0xa0, 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b,
0x9f, 0x43, 0x00, 0x75, 0x00, 0x73, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x6d,
0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00,
0xff, 0xaa, 0x55, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa,
0x55, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c,
0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x9f, 0x04, 0x19, 0x4c, 0x37,
0x41, 0xd3, 0x4d, 0x9c, 0x10, 0x8b, 0x97, 0xa8, 0x3f, 0xfd, 0xfa, 0x4d,
0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x79, 0x00, 0x54,
0x00, 0x79, 0x00, 0x70, 0x00, 0x65, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x66,
0x00, 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69,
0x00, 0x6f, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x80,
0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x55, 0x3c, 0x00, 0x03,
0x00, 0xd5, 0xf6, 0x56, 0xcb, 0x8f, 0xe8, 0xa2, 0x5c, 0x62, 0x68, 0xd1,
0x3d, 0x94, 0x90, 0x5b, 0xd7, 0xce, 0x9a, 0x18, 0xc4, 0x30, 0x56, 0x06,
0x03, 0x55, 0x1d, 0x1f, 0x04, 0x4f, 0x30, 0x4d, 0x30, 0x4b, 0xa0, 0x49,
0xa0, 0x47, 0x86, 0x45, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x63, 0x72, 0x6c,
0x2f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x2f, 0x4d, 0x69,
0x63, 0x52, 0x6f, 0x6f, 0x43, 0x65, 0x72, 0x41, 0x75, 0x74, 0x5f, 0x32,
0x30, 0x31, 0x30, 0x2d, 0x30, 0x36, 0x2d, 0x32, 0x33, 0x2e, 0x63, 0x72,
0x6c, 0x30, 0x5a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
0x01, 0x04, 0x4e, 0x30, 0x4c, 0x30, 0x4a, 0x06, 0x08, 0x2b, 0x06, 0x01,
0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68, 0x74, 0x74, 0x70, 0x3a,
0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73,
0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x0c, 0x00, 0x00, 0x00, 0x22, 0x00,
0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d,
0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00,
0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41,
0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f,
0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00,
0x7f, 0xff, 0x04, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x49, 0x00,
0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d,
0x00, 0xe0, 0x98, 0x03, 0x03, 0x0f, 0x0b, 0x00, 0xff, 0xff, 0xff, 0xff,
0x03, 0x01, 0x01, 0x7f, 0x01, 0x04, 0x00, 0x01, 0x04, 0x14, 0x00, 0x9b,
0x5a, 0x5a, 0x86, 0x5d, 0xb8, 0x4c, 0x47, 0x84, 0x55, 0x65, 0xd1, 0xbe,
0x84, 0x4b, 0xe2, 0x03, 0x0e, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0a,
0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0, 0xbe, 0x8d, 0x2b, 0xf1, 0xff, 0x96,
0x76, 0x8b, 0x4c, 0xa9, 0x85, 0x27, 0x47, 0x07, 0x5b, 0x4f, 0x50, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x46, 0x56, 0x48, 0xff,
0xfe, 0x04, 0x00, 0x48, 0x00, 0x19, 0xf9, 0x00, 0x00, 0x00, 0x02, 0x20,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x78, 0x2c, 0xf3, 0xaa, 0x7b, 0x94, 0x9a, 0x43, 0xa1,
0x80, 0x2e, 0x14, 0x4e, 0xc3, 0x77, 0x92, 0xb8, 0xdf, 0x00, 0x00, 0x5a,
0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x55, 0x3c, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x0c, 0xec, 0x76, 0xc0, 0x28, 0x70, 0x99, 0x43, 0xa0,
0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f, 0xf1, 0x00, 0x00, 0x00, 0x61,
0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98,
0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75,
0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02,
0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x03,
0x0e, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0xff,
0xff, 0xaa, 0x55, 0x3f, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xe4, 0x73,
0x90, 0xec, 0x60, 0x6e, 0x4b, 0x99, 0x03, 0x4c, 0x22, 0x3c, 0x26, 0x0f,
0x3c, 0x56, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x72,
0x00, 0x4b, 0x00, 0x65, 0x00, 0x79, 0x00, 0x73, 0x00, 0x4e, 0x00, 0x76,
0x00, 0x00, 0x00, 0x00, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0xc7, 0x0b, 0xa3, 0xf0, 0x08, 0xaf, 0x56, 0x45, 0x99, 0xc4, 0x00,
0x10, 0x09, 0xc9, 0x3a, 0x44, 0x53, 0x00, 0x65, 0x00, 0x63, 0x00, 0x75,
0x00, 0x72, 0x00, 0x65, 0x00, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74,
0x00, 0x45, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65,
0x00, 0x00, 0x00, 0x01, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x00, 0x00, 0xff,
0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0xd6,
0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f,
0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00,
0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00,
0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00,
0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0xcb, 0xb2, 0x19, 0xd7, 0x3a, 0x3d,
0x96, 0x45, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f, 0x64, 0x00,
0x62, 0x00, 0x78, 0x00, 0x00, 0x00, 0x26, 0x16, 0xc4, 0xc1, 0x4c, 0x50,
0x92, 0x40, 0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28, 0x4c, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xa3, 0xa8,
0xba, 0xa0, 0x1d, 0x04, 0xa8, 0x48, 0xbc, 0x87, 0xc3, 0x6d, 0x12, 0x1b,
0x5e, 0x3d, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb,
0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, 0x26, 0x16,
0xc4, 0xc1, 0x4c, 0x50, 0x92, 0x40, 0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93,
0x43, 0x28, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x40,
0x70, 0xeb, 0x02, 0x14, 0xd3, 0x11, 0x8e, 0x77, 0x00, 0xa0, 0xc9, 0x69,
0x72, 0x3b, 0x4d, 0x00, 0x54, 0x00, 0x43, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0xaa, 0x55, 0x3c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0xd6,
0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f,
0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00,
0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00,
0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00,
0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xaa, 0x55,
0x3f, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00,
0x00, 0x00, 0x19, 0x04, 0x00, 0x00, 0x45, 0x49, 0x32, 0x59, 0x44, 0xec,
0x0d, 0x4c, 0xb1, 0xcd, 0x9d, 0xb1, 0x39, 0xdf, 0x07, 0x0c, 0x41, 0x00,
0x74, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00,
0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x74, 0x74, 0x65,
0x6d, 0x70, 0x74, 0x20, 0x31, 0x00, 0xf5, 0x2f, 0x83, 0xa3, 0xfa, 0x9c,
0xfb, 0xd6, 0x92, 0x0f, 0x72, 0x28, 0x24, 0xdb, 0xe4, 0x03, 0x45, 0x34,
0xd2, 0x5b, 0x85, 0x07, 0x24, 0x6b, 0x3b, 0x95, 0x7d, 0xac, 0x6e, 0x1b,
0xce, 0x7a, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60,
0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc5, 0xd9, 0xd8, 0xa1, 0x86, 0xe2,
0xc8, 0x2d, 0x09, 0xaf, 0xaa, 0x2a, 0x6f, 0x7f, 0x2e, 0x73, 0x87, 0x0d,
0x3e, 0x64, 0xf7, 0x2c, 0x4e, 0x08, 0xef, 0x67, 0x79, 0x6a, 0x84, 0x0f,
0x0f, 0xbd, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60,
0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x1a, 0xec, 0x84, 0xb8, 0x4b, 0x6c,
0x65, 0xa5, 0x12, 0x20, 0xa9, 0xbe, 0x71, 0x81, 0x96, 0x52, 0x30, 0x21,
0x0d, 0x62, 0xd6, 0xd3, 0x3c, 0x48, 0x99, 0x9c, 0x6b, 0x29, 0x5a, 0x2b,
0x0a, 0x06, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60,
0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc3, 0xa9, 0x9a, 0x46, 0x0d, 0xa4,
0x64, 0xa0, 0x57, 0xc3, 0x58, 0x6d, 0x83, 0xce, 0xf5, 0xf4, 0xae, 0x08,
0xb7, 0x10, 0x39, 0x79, 0xed, 0x89, 0x32, 0x74, 0x2d, 0xf0, 0xed, 0x53,
0x0c, 0x66, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60,
0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x58, 0xfb, 0x94, 0x1a, 0xef, 0x95,
0xa2, 0x59, 0x43, 0xb3, 0xfb, 0x5f, 0x25, 0x10, 0xa0, 0xdf, 0x3f, 0xe4,
0x4c, 0x58, 0xc9, 0x5e, 0x0a, 0xb8, 0x04, 0x87, 0x29, 0x75, 0x68, 0xab,
0x97, 0x71, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60,
0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x53, 0x91, 0xc3, 0xa2, 0xfb, 0x11,
0x21, 0x02, 0xa6, 0xaa, 0x1e, 0xdc, 0x25, 0xae, 0x77, 0xe1, 0x9f, 0x5d,
0x6f, 0x09, 0xcd, 0x09, 0xee, 0xb2, 0x50, 0x99, 0x22, 0xbf, 0xcd, 0x59,
0x92, 0xea, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60,
0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xd6, 0x26, 0x15, 0x7e, 0x1d, 0x6a,
0x71, 0x8b, 0xc1, 0x24, 0xab, 0x8d, 0xa2, 0x7c, 0xbb, 0x65, 0x07, 0x2c,
0xa0, 0x3a, 0x7b, 0x6b, 0x25, 0x7d, 0xbd, 0xcb, 0xbd, 0x60, 0xf6, 0x5e,
0xf3, 0xd1, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60,
0x28, 0xf4, 0xe7, 0xff, 0x04, 0x00, 0xff, 0xff, 0xff, 0xaa, 0x55, 0x3c,
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00,
0x00, 0xc6, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0xff, 0x04, 0x00, 0xaa, 0x55, 0x3c,
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,
0x00, 0xb4, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x28, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52,
0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e,
0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41,
0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74,
0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xaa, 0x55, 0x3f,
0x00, 0x03, 0x00, 0xdd, 0x0c, 0xbb, 0xa2, 0xe4, 0x2e, 0x09, 0xe3, 0xe7,
0xc5, 0xf7, 0x96, 0x69, 0xbc, 0x00, 0x21, 0xbd, 0x69, 0x33, 0x33, 0xef,
0xad, 0x04, 0xcb, 0x54, 0x80, 0xee, 0x06, 0x83, 0xbb, 0xc5, 0x20, 0x84,
0xd9, 0xf7, 0xd2, 0x8b, 0xf3, 0x38, 0xb0, 0xab, 0xa4, 0xad, 0x2d, 0x7c,
0x62, 0x79, 0x05, 0xff, 0xe3, 0x4a, 0x3f, 0x04, 0x35, 0x20, 0x70, 0xe3,
0xc4, 0xe7, 0x6b, 0xe0, 0x9c, 0xc0, 0x36, 0x75, 0xe9, 0x8a, 0x31, 0xdd,
0x8d, 0x70, 0xe5, 0xdc, 0x37, 0xb5, 0x74, 0x46, 0x96, 0x28, 0x5b, 0x87,
0x60, 0x23, 0x2c, 0xbf, 0xdc, 0x47, 0xa5, 0x67, 0xf7, 0x51, 0x27, 0x9e,
0x72, 0xeb, 0x07, 0xa6, 0xc9, 0xb9, 0x1e, 0x3b, 0x53, 0x35, 0x7c, 0xe5,
0xd3, 0xec, 0x27, 0xb9, 0x87, 0x1c, 0xfe, 0xb9, 0xc9, 0x23, 0x09, 0x6f,
0xa8, 0x46, 0x91, 0xc1, 0x6e, 0x96, 0x3c, 0x41, 0xd3, 0xcb, 0xa3, 0x3f,
0x5d, 0x02, 0x6a, 0x4d, 0xec, 0x69, 0x1f, 0x25, 0x28, 0x5c, 0x36, 0xff,
0xfd, 0x43, 0x15, 0x0a, 0x94, 0xe0, 0x19, 0xb4, 0xcf, 0xdf, 0xc2, 0x12,
0xe2, 0xc2, 0x5b, 0x27, 0xee, 0x27, 0x78, 0x30, 0x8b, 0x5b, 0x2a, 0x09,
0x6b, 0x22, 0x89, 0x53, 0x60, 0x16, 0x2c, 0xc0, 0x68, 0x1d, 0x53, 0xba,
0xec, 0x49, 0xf3, 0x9d, 0x61, 0x8c, 0x85, 0x68, 0x09, 0x73, 0x44, 0x5d,
0x7d, 0xa2, 0x54, 0x2b, 0xdd, 0x79, 0xf7, 0x15, 0xcf, 0x35, 0x5d, 0x6c,
0x1c, 0x2b, 0x5c, 0xce, 0xbc, 0x9c, 0x23, 0x8b, 0x6f, 0x6e, 0xb5, 0x26,
0xd9, 0x36, 0x13, 0xc3, 0x4f, 0xd6, 0x27, 0xae, 0xb9, 0x32, 0x3b, 0x41,
0x92, 0x2c, 0xe1, 0xc7, 0xcd, 0x77, 0xe8, 0xaa, 0x54, 0x4e, 0xf7, 0x5c,
0x0b, 0x04, 0x87, 0x65, 0xb4, 0x43, 0x18, 0xa8, 0xb2, 0xe0, 0x6d, 0x19,
0x77, 0xec, 0x5a, 0x24, 0xfa, 0x48, 0x03, 0x02, 0x03, 0x01, 0x00, 0x01,
0xa3, 0x82, 0x01, 0x43, 0x30, 0x82, 0x01, 0x3f, 0x30, 0x10, 0x06, 0x09,
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02,
0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04,
0x14, 0xa9, 0x29, 0x02, 0x39, 0x8e, 0x16, 0xc4, 0x97, 0x78, 0xcd, 0x90,
0xf9, 0x9e, 0x4f, 0x9a, 0xe1, 0x7c, 0x55, 0xaf, 0x53, 0x30, 0x19, 0x06,
0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c,
0x1e, 0x0a, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41,
0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01,
0x86, 0x30, 0x0f, 0xc4, 0xe8, 0xb5, 0x8a, 0xbf, 0xad, 0x57, 0x26, 0xb0,
0x26, 0xc3, 0xea, 0xe7, 0xfb, 0x57, 0x7a, 0x44, 0x02, 0x5d, 0x07, 0x0d,
0xda, 0x4a, 0xe5, 0x74, 0x2a, 0xe6, 0xb0, 0x0f, 0xec, 0x6d, 0xeb, 0xec,
0x7f, 0xb9, 0xe3, 0x5a, 0x63, 0x32, 0x7c, 0x11, 0x17, 0x4f, 0x0e, 0xe3,
0x0b, 0xa7, 0x38, 0x15, 0x93, 0x8e, 0xc6, 0xf5, 0xe0, 0x84, 0xb1, 0x9a,
0x9b, 0x2c, 0xe7, 0xf5, 0xb7, 0x91, 0xd6, 0x09, 0xe1, 0xe2, 0xc0, 0x04,
0xa8, 0xac, 0x30, 0x1c, 0xdf, 0x48, 0xf3, 0x06, 0x50, 0x9a, 0x64, 0xa7,
0x51, 0x7f, 0xc8, 0x85, 0x4f, 0x8f, 0x20, 0x86, 0xce, 0xfe, 0x2f, 0xe1,
0x9f, 0xff, 0x82, 0xc0, 0xed, 0xe9, 0xcd, 0xce, 0xf4, 0x53, 0x6a, 0x62,
0x3a, 0x0b, 0x43, 0xb9, 0xe2, 0x25, 0xfd, 0xfe, 0x05, 0xf9, 0xd4, 0xc4,
0x14, 0xab, 0x11, 0xe2, 0x23, 0x89, 0x8d, 0x70, 0xb7, 0xa4, 0x1d, 0x4d,
0xec, 0xae, 0xe5, 0x9c, 0xfa, 0x16, 0xc2, 0xd7, 0xc1, 0xcb, 0xd4, 0xe8,
0xc4, 0x2f, 0xe5, 0x99, 0xee, 0x24, 0x8b, 0x03, 0xec, 0x8d, 0xf2, 0x8b,
0xea, 0xc3, 0x4a, 0xfb, 0x43, 0x11, 0x12, 0x0b, 0x7e, 0xb5, 0x47, 0x92,
0x6c, 0xdc, 0xe6, 0x04, 0x89, 0xeb, 0xf5, 0x33, 0x04, 0xeb, 0x10, 0x01,
0x2a, 0x71, 0xe5, 0xf9, 0x83, 0x13, 0x3c, 0xff, 0x25, 0x09, 0x2f, 0x68,
0x76, 0x46, 0xff, 0xba, 0x4f, 0xbe, 0xdc, 0xad, 0x71, 0x2a, 0x58, 0xaa,
0xfb, 0x0e, 0xd2, 0x79, 0x3d, 0xe4, 0x9b, 0x65, 0x3b, 0xcc, 0x29, 0x2a,
0x9f, 0xfc, 0x72, 0x59, 0xa2, 0xeb, 0xae, 0x92, 0xef, 0xf6, 0x35, 0x13,
0x80, 0xc6, 0x02, 0xec, 0xe4, 0x5f, 0xcc, 0x9d, 0x76, 0xcd, 0xef, 0x63,
0x92, 0xc1, 0xaf, 0x79, 0x40, 0x84, 0x79, 0x87, 0x7f, 0xe3, 0x52, 0xa8,
0xe8, 0x9d, 0x7b, 0x07, 0x69, 0x8f, 0x15, 0x02, 0x03, 0x01, 0x00, 0x01,
0xa3, 0x82, 0x01, 0x4f, 0x30, 0x82, 0x01, 0x4b, 0x30, 0x10, 0x06, 0x09,
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02,
0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04,
0x14, 0x62, 0xfc, 0x43, 0xcd, 0xa0, 0x3e, 0xa4, 0xcb, 0x67, 0x12, 0xd2,
0x5b, 0xd9, 0x55, 0xac, 0x7b, 0xcc, 0xb6, 0x8a, 0x5f, 0x30, 0x19, 0x06,
0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c,
0x1e, 0x0a, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41,
0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01,
0x86, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1f, 0x06, 0xff, 0x04, 0x00,
0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0e, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4,
0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b,
0x35, 0x08, 0x42, 0xff, 0x30, 0xcc, 0xce, 0xf7, 0x76, 0x0c, 0xad, 0x10,
0x68, 0x58, 0x35, 0x29, 0x46, 0x32, 0x76, 0x27, 0x7c, 0xef, 0x12, 0x41,
0x27, 0x42, 0x1b, 0x4a, 0xaa, 0x6d, 0x81, 0x38, 0x48, 0x59, 0x13, 0x55,
0xf3, 0xe9, 0x58, 0x34, 0xa6, 0x16, 0x0b, 0x82, 0xaa, 0x5d, 0xad, 0x82,
0xda, 0x80, 0x83, 0x41, 0x06, 0x8f, 0xb4, 0x1d, 0xf2, 0x03, 0xb9, 0xf3,
0x1a, 0x5d, 0x1b, 0xf1, 0x50, 0x90, 0xf9, 0xb3, 0x55, 0x84, 0x42, 0x28,
0x1c, 0x20, 0xbd, 0xb2, 0xae, 0x51, 0x14, 0xc5, 0xc0, 0xac, 0x97, 0x95,
0x21, 0x1c, 0x90, 0xdb, 0x0f, 0xfc, 0x77, 0x9e, 0x95, 0x73, 0x91, 0x88,
0xca, 0xbd, 0xbd, 0x52, 0xb9, 0x05, 0x50, 0x0d, 0xdf, 0x57, 0x9e, 0xa0,
0x61, 0xed, 0x0d, 0xe5, 0x6d, 0x25, 0xd9, 0x40, 0x0f, 0x17, 0x40, 0xc8,
0xce, 0xa3, 0x4a, 0xc2, 0x4d, 0xaf, 0x9a, 0x12, 0x1d, 0x08, 0x54, 0x8f,
0xbd, 0xc7, 0xbc, 0xb9, 0x2b, 0x3d, 0x49, 0x2b, 0x1f, 0x32, 0xfc, 0x6a,
0x21, 0x69, 0x4f, 0x9b, 0xc8, 0x7e, 0x42, 0x34, 0xfc, 0x36, 0x06, 0x17,
0x8b, 0x8f, 0x20, 0x40, 0xc0, 0xb3, 0x9a, 0x25, 0x75, 0x27, 0xcd, 0xc9,
0x03, 0xa3, 0xf6, 0x5d, 0xd1, 0xe7, 0x36, 0x54, 0x7a, 0xb9, 0x50, 0xb5,
0xd3, 0x12, 0xd1, 0x07, 0xbf, 0xbb, 0x74, 0xdf, 0xdc, 0x1e, 0x8f, 0x80,
0xd5, 0xed, 0x18, 0xf4, 0x2f, 0x14, 0x16, 0x6b, 0x2f, 0xde, 0x66, 0x8c,
0xb0, 0x23, 0xe5, 0xc7, 0x84, 0xd8, 0xed, 0xea, 0xc1, 0x33, 0x82, 0xad,
0x56, 0x4b, 0x18, 0x2d, 0xf1, 0x68, 0x95, 0x07, 0xcd, 0xcf, 0xf0, 0x72,
0xf0, 0xae, 0xbb, 0xdd, 0x86, 0x85, 0x98, 0x2c, 0x21, 0x4c, 0x33, 0x2b,
0xf0, 0x0f, 0x4a, 0xf0, 0x68, 0x87, 0xb5, 0x92, 0x55, 0x32, 0x75, 0xa1,
0x6a, 0x82, 0x6a, 0x3c, 0xa3, 0x25, 0x11, 0xa4, 0xed, 0xad, 0xd7, 0x04,
0xae, 0xcb, 0xd8, 0x40, 0x59, 0xa0, 0x84, 0xd1, 0x95, 0x4c, 0x62, 0x91,
0x22, 0x1a, 0x74, 0x1d, 0x8c, 0x3d, 0x47, 0x0e, 0x44, 0xa6, 0xe4, 0xb0,
0x9b, 0x34, 0x35, 0xb1, 0xfa, 0xb6, 0x53, 0xa8, 0x2c, 0x81, 0xec, 0xa4,
0x05, 0x71, 0xc8, 0x9d, 0xb8, 0xba, 0xe8, 0x1b, 0x44, 0x66, 0xe4, 0x47,
0x54, 0x0e, 0x8e, 0x56, 0x7f, 0xb3, 0x9f, 0x16, 0x98, 0xb2, 0x86, 0xd0,
0x68, 0x3e, 0x90, 0x23, 0xb5, 0x2f, 0x5e, 0x8f, 0x50, 0x85, 0x8d, 0xc6,
0x8d, 0x82, 0x5f, 0x41, 0xa1, 0xf4, 0x2e, 0x0d, 0xe0, 0x99, 0xd2, 0x6c,
0x75, 0xe4, 0xb6, 0x69, 0xb5, 0x21, 0x86, 0xfa, 0x07, 0xd1, 0xf6, 0xe2,
0x4d, 0xd1, 0xda, 0xad, 0x2c, 0x77, 0x53, 0x1e, 0x25, 0x32, 0x37, 0xc7,
0x6c, 0x52, 0x72, 0x95, 0x86, 0xb0, 0xf1, 0x35, 0x61, 0x6a, 0x19, 0xf5,
0xb2, 0x3b, 0x81, 0x50, 0x56, 0xa6, 0x32, 0x2d, 0xfe, 0xa2, 0x89, 0xf9,
0x42, 0x86, 0x27, 0x18, 0x55, 0xa1, 0x82, 0xca, 0x5a, 0x9b, 0xf8, 0x30,
0x98, 0x54, 0x14, 0xa6, 0x47, 0x96, 0x25, 0x2f, 0xc8, 0x26, 0xe4, 0x41,
0x94, 0x1a, 0x5c, 0x02, 0x3f, 0xe5, 0x96, 0xe3, 0x85, 0x5b, 0x3c, 0x3e,
0x3f, 0xbb, 0x47, 0x16, 0x72, 0x55, 0xe2, 0x25, 0x22, 0xb1, 0xd9, 0x7b,
0xe7, 0x03, 0x06, 0x2a, 0xa3, 0xf7, 0x1e, 0x90, 0x46, 0xc3, 0x00, 0x0d,
0xd6, 0x19, 0x89, 0xe3, 0x0e, 0x35, 0x27, 0x62, 0x03, 0x71, 0x15, 0xa6,
0xef, 0xd0, 0x27, 0xa0, 0xa0, 0x59, 0x37, 0x60, 0xf8, 0x38, 0x94, 0xb8,
0xe0, 0x78, 0x70, 0xf8, 0xba, 0x4c, 0x86, 0x87, 0x94, 0xf6, 0xe0, 0xae,
0x02, 0x45, 0xee, 0x65, 0xc2, 0xb6, 0xa3, 0x7e, 0x69, 0x16, 0x75, 0x07,
0x92, 0x9b, 0xf5, 0xa6, 0xbc, 0x59, 0x83, 0x58, 0xff, 0xff, 0xff, 0xaa,
0x55, 0x3c, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe4, 0x07, 0x09, 0x10, 0x10, 0x36, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
0x00, 0x02, 0x02, 0x09, 0x00, 0xfe, 0xf5, 0x88, 0xe8, 0xf3, 0x96, 0xc0,
0xf1, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
0x01, 0x0b, 0x05, 0x00, 0x30, 0x51, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03,
0x55, 0x04, 0x03, 0x13, 0x22, 0x52, 0x65, 0x64, 0x20, 0x48, 0x61, 0x74,
0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x42, 0x6f, 0x6f, 0x74,
0x20, 0x28, 0x50, 0x4b, 0x2f, 0x4b, 0x45, 0x4b, 0x20, 0x6b, 0x65, 0x79,
0x20, 0x31, 0x29, 0x31, 0x22, 0x30, 0x20, 0x06, 0x09, 0x2a, 0x86, 0x48,
0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x13, 0x73, 0x65, 0x63, 0x61,
0x6c, 0x65, 0x72, 0x74, 0x40, 0x72, 0x65, 0x64, 0x68, 0x61, 0x74, 0x2e,
0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x31, 0x30, 0x33,
0x31, 0x31, 0x31, 0x31, 0x35, 0x33, 0x37, 0x5a, 0x17, 0x0d, 0x33, 0x37,
0x31, 0x30, 0x32, 0x35, 0x31, 0x31, 0x31, 0x35, 0x33, 0x37, 0x5a, 0x30,
0x51, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22,
0x52, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x61,
0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98,
0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30,
0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,
0x00, 0x2c, 0x00, 0x55, 0x00, 0x69, 0x00, 0x41, 0x00, 0x70, 0x00, 0x70,
0x00, 0x00, 0x00, 0x04, 0x07, 0x14, 0xd4, 0x84, 0x88, 0xf5, 0x14, 0x94,
0x18, 0x02, 0xca, 0x2a, 0x3c, 0xfb, 0x2a, 0x92, 0x1c, 0x0c, 0xd7, 0xa0,
0xd1, 0xf1, 0xe8, 0x52, 0x66, 0xa8, 0xee, 0xa2, 0xb5, 0x75, 0x7a, 0x90,
0x00, 0xaa, 0x2d, 0xa4, 0x76, 0x5a, 0xea, 0x79, 0xb7, 0xb9, 0x37, 0x6a,
0x51, 0x7b, 0x10, 0x64, 0xf6, 0xe1, 0x64, 0xf2, 0x02, 0x67, 0xbe, 0xf7,
0xa8, 0x1b, 0x78, 0xbd, 0xba, 0xce, 0x88, 0x58, 0x64, 0x0c, 0xd6, 0x57,
0xc8, 0x19, 0xa3, 0x5f, 0x05, 0xd6, 0xdb, 0xc6, 0xd0, 0x69, 0xce, 0x48,
0x4b, 0x32, 0xb7, 0xeb, 0x5d, 0xd2, 0x30, 0xf5, 0xc0, 0xf5, 0xb8, 0xba,
0x78, 0x07, 0xa3, 0x2b, 0xfe, 0x9b, 0xdb, 0x34, 0x56, 0x84, 0xec, 0x82,
0xca, 0xae, 0x41, 0x25, 0x70, 0x9c, 0x6b, 0xe9, 0xfe, 0x90, 0x0f, 0xd7,
0x96, 0x1f, 0xe5, 0xe7, 0x94, 0x1f, 0xb2, 0x2a, 0x0c, 0x8d, 0x4b, 0xff,
0x28, 0x29, 0x10, 0x7b, 0xf7, 0xd7, 0x7c, 0xa5, 0xd1, 0x76, 0xb9, 0x05,
0xc8, 0x79, 0xed, 0x0f, 0x90, 0x92, 0x9c, 0xc2, 0xfe, 0xdf, 0x6f, 0x7e,
0x6c, 0x0f, 0x7b, 0xd4, 0xc1, 0x45, 0xdd, 0x34, 0x51, 0x96, 0x39, 0x0f,
0xe5, 0x5e, 0x56, 0xd8, 0x18, 0x05, 0x96, 0xf4, 0x07, 0xa6, 0x42, 0xb3,
0xa0, 0x77, 0xfd, 0x08, 0x19, 0xf2, 0x71, 0x56, 0xcc, 0x9f, 0x86, 0x23,
0xa4, 0x87, 0xcb, 0xa6, 0xfd, 0x58, 0x7e, 0xd4, 0x69, 0x67, 0x15, 0x91,
0x7e, 0x81, 0xf2, 0x7f, 0x13, 0xe5, 0x0d, 0x8b, 0x8a, 0x3c, 0x87, 0x84,
0xeb, 0xe3, 0xce, 0xbd, 0x43, 0xe5, 0xad, 0x2d, 0x84, 0x93, 0x8e, 0x6a,
0x2b, 0x5a, 0x7c, 0x44, 0xfa, 0x52, 0xaa, 0x81, 0xc8, 0x2d, 0x1c, 0xbb,
0xe0, 0x52, 0xdf, 0x00, 0x11, 0xf8, 0x9a, 0x3d, 0xc1, 0x60, 0xb0, 0xe1,
0x33, 0xb5, 0xa3, 0x88, 0xd1, 0x65, 0x19, 0x0a, 0x1a, 0xe7, 0xac, 0x7c,
0xa4, 0xc1, 0x82, 0x87, 0x4e, 0x38, 0xb1, 0x2f, 0x0d, 0xc5, 0x14, 0x87,
0x6f, 0xfd, 0x8d, 0x2e, 0xbc, 0x39, 0xb6, 0xe7, 0xe6, 0xc3, 0xe0, 0xe4,
0xcd, 0x27, 0x84, 0xef, 0x94, 0x42, 0xef, 0x29, 0x8b, 0x90, 0x46, 0x41,
0x3b, 0x81, 0x1b, 0x67, 0xd8, 0xf9, 0x43, 0x59, 0x65, 0xcb, 0x0d, 0xbc,
0xfd, 0x00, 0x92, 0x4f, 0xf4, 0x75, 0x3b, 0xa7, 0xa9, 0x24, 0xfc, 0x50,
0x41, 0x40, 0x79, 0xe0, 0x2d, 0x4f, 0x0a, 0x6a, 0x27, 0x76, 0x6e, 0x52,
0xed, 0x96, 0x69, 0x7b, 0xaf, 0x0f, 0xf7, 0x87, 0x05, 0xd0, 0x45, 0xc2,
0xad, 0x53, 0x14, 0x81, 0x1f, 0xfb, 0x30, 0x04, 0xaa, 0x37, 0x36, 0x61,
0xda, 0x4a, 0x69, 0x1b, 0x34, 0xd8, 0x68, 0xed, 0xd6, 0x02, 0xcf, 0x6c,
0x94, 0x0c, 0xd3, 0xcf, 0x6c, 0x22, 0x79, 0xad, 0xb1, 0xf0, 0xbc, 0x03,
0xa2, 0x46, 0x60, 0xa9, 0xc4, 0x07, 0xc2, 0x21, 0x82, 0xf1, 0xfd, 0xf2,
0xe8, 0x79, 0x32, 0x60, 0xbf, 0xd8, 0xac, 0xa5, 0x22, 0x14, 0x4b, 0xca,
0xc1, 0xd8, 0x4b, 0xeb, 0x7d, 0x3f, 0x57, 0x35, 0xb2, 0xe6, 0x4f, 0x75,
0xb4, 0xb0, 0x60, 0x03, 0x22, 0x53, 0xae, 0x91, 0x79, 0x1d, 0xd6, 0x9b,
0x41, 0x1f, 0x15, 0x86, 0x54, 0x70, 0xb2, 0xde, 0x0d, 0x35, 0x0f, 0x7c,
0xb0, 0x34, 0x72, 0xba, 0x97, 0x60, 0x3b, 0xf0, 0x79, 0xeb, 0xa2, 0xb2,
0x1c, 0x5d, 0xa2, 0x16, 0xb8, 0x87, 0xc5, 0xe9, 0x1b, 0xf6, 0xb5, 0x97,
0x25, 0x6f, 0x38, 0x9f, 0xe3, 0x91, 0xfa, 0x8a, 0x79, 0x98, 0xc3, 0x69,
0x0e, 0xb7, 0xa3, 0x1c, 0x20, 0x05, 0x97, 0xf8, 0xca, 0x14, 0xae, 0x00,
0xd7, 0xc4, 0xf3, 0xc0, 0x14, 0x10, 0x75, 0x6b, 0x34, 0xa0, 0x1b, 0xb5,
0x99, 0x60, 0xf3, 0x5c, 0xb0, 0xc5, 0x57, 0x4e, 0x36, 0xd2, 0x32, 0x84,
0xbf, 0x9e, 0xaa, 0x55, 0x3f, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x07, 0x09, 0x10, 0x10, 0x36,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0x03, 0x00, 0x00, 0x61, 0xdf,
0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xd0, 0x63, 0xec,
0x28, 0xf6, 0x7e, 0xba, 0x53, 0xf1, 0x64, 0x2d, 0xbf, 0x7d, 0xff, 0x33,
0xc6, 0xa3, 0x2a, 0xdd, 0x86, 0x9f, 0x60, 0x13, 0xfe, 0x16, 0x2e, 0x2c,
0x32, 0xf1, 0xcb, 0xe5, 0x6d, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x29, 0xc6, 0xeb,
0x52, 0xb4, 0x3c, 0x3a, 0xa1, 0x8b, 0x2c, 0xd8, 0xed, 0x6e, 0xa8, 0x60,
0x7c, 0xef, 0x3c, 0xfa, 0xe1, 0xba, 0xfe, 0x11, 0x65, 0x75, 0x5c, 0xf2,
0xe6, 0x14, 0x84, 0x4a, 0x44, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x90, 0xfb, 0xe7,
0x0e, 0x69, 0xd6, 0x33, 0x40, 0x8d, 0x3e, 0x17, 0x0c, 0x68, 0x32, 0xdb,
0xb2, 0xd2, 0x09, 0xe0, 0x27, 0x25, 0x27, 0xdf, 0xb6, 0x3d, 0x49, 0xd2,
0x95, 0x72, 0xa6, 0xf4, 0x4c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x10, 0x6f, 0xac,
0xea, 0xcf, 0xec, 0xfd, 0x4e, 0x30, 0x3b, 0x74, 0xf4, 0x80, 0xa0, 0x80,
0x98, 0xe2, 0xd0, 0x80, 0x2b, 0x93, 0x6f, 0x8e, 0xc7, 0x74, 0xce, 0x21,
0xf3, 0x16, 0x86, 0x68, 0x9c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x17, 0x4e, 0x3a,
0x0b, 0x5b, 0x43, 0xc6, 0xa6, 0x07, 0xbb, 0xd3, 0x40, 0x4f, 0x05, 0x34,
0x1e, 0x3d, 0xcf, 0x39, 0x62, 0x67, 0xce, 0x94, 0xf8, 0xb5, 0x0e, 0x2e,
0x23, 0xa9, 0xda, 0x92, 0x0c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x2b, 0x99, 0xcf,
0x26, 0x42, 0x2e, 0x92, 0xfe, 0x36, 0x5f, 0xbf, 0x4b, 0xc3, 0x0d, 0x27,
0x08, 0x6c, 0x9e, 0xe1, 0x4b, 0x7a, 0x6f, 0xff, 0x44, 0xfb, 0x2f, 0x6b,
0x90, 0x01, 0x69, 0x99, 0x39, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x2e, 0x70, 0x91,
0x67, 0x86, 0xa6, 0xf7, 0x73, 0x51, 0x1f, 0xa7, 0x18, 0x1f, 0xab, 0x0f,
0x1d, 0x70, 0xb5, 0x57, 0xc6, 0x32, 0x2e, 0xa9, 0x23, 0xb2, 0xa8, 0xd3,
0xb9, 0x2b, 0x51, 0xaf, 0x7d, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x3f, 0xce, 0x9b,
0x9f, 0xdf, 0x3e, 0xf0, 0x9d, 0x54, 0x52, 0xb0, 0xf9, 0x5e, 0xe4, 0x81,
0xc2, 0xb7, 0xf0, 0x6d, 0x74, 0x3a, 0x73, 0x79, 0x71, 0x55, 0x8e, 0x70,
0x13, 0x6a, 0xce, 0x3e, 0x73, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x47, 0xcc, 0x08,
0x61, 0x27, 0xe2, 0x06, 0x9a, 0x86, 0xe0, 0x3a, 0x6b, 0xef, 0x2c, 0xd4,
0x10, 0xf8, 0xc5, 0x5a, 0x6d, 0x6b, 0xdb, 0x36, 0x21, 0x68, 0xc3, 0x1b,
0x2c, 0xe3, 0x2a, 0x5a, 0xdf, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x71, 0xf2, 0x90,
0x6f, 0xd2, 0x22, 0x49, 0x7e, 0x54, 0xa3, 0x46, 0x62, 0xab, 0x24, 0x97,
0xfc, 0xc8, 0x10, 0x20, 0x77, 0x0f, 0xf5, 0x13, 0x68, 0xe9, 0xe3, 0xd9,
0xbf, 0xcb, 0xfd, 0x63, 0x75, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x82, 0xdb, 0x3b,
0xce, 0xb4, 0xf6, 0x08, 0x43, 0xce, 0x9d, 0x97, 0xc3, 0xd1, 0x87, 0xcd,
0x9b, 0x59, 0x41, 0xcd, 0x3d, 0xe8, 0x10, 0x0e, 0x58, 0x6f, 0x2b, 0xda,
0x56, 0x37, 0x57, 0x5f, 0x67, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x8a, 0xd6, 0x48,
0x59, 0xf1, 0x95, 0xb5, 0xf5, 0x8d, 0xaf, 0xaa, 0x94, 0x0b, 0x6a, 0x61,
0x67, 0xac, 0xd6, 0x7a, 0x88, 0x6e, 0x8f, 0x46, 0x93, 0x64, 0x17, 0x72,
0x21, 0xc5, 0x59, 0x45, 0xb9, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x8d, 0x8e, 0xa2,
0x89, 0xcf, 0xe7, 0x0a, 0x1c, 0x07, 0xab, 0x73, 0x65, 0xcb, 0x28, 0xee,
0x51, 0xed, 0xd3, 0x3c, 0xf2, 0x50, 0x6d, 0xe8, 0x88, 0xfb, 0xad, 0xd6,
0x0e, 0xbf, 0x80, 0x48, 0x1c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xae, 0xeb, 0xae,
0x31, 0x51, 0x27, 0x12, 0x73, 0xed, 0x95, 0xaa, 0x2e, 0x67, 0x11, 0x39,
0xed, 0x31, 0xa9, 0x85, 0x67, 0x30, 0x3a, 0x33, 0x22, 0x98, 0xf8, 0x37,
0x09, 0xa9, 0xd5, 0x5a, 0xa1, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc4, 0x09, 0xbd,
0xac, 0x47, 0x75, 0xad, 0xd8, 0xdb, 0x92, 0xaa, 0x22, 0xb5, 0xb7, 0x18,
0xfb, 0x8c, 0x94, 0xa1, 0x46, 0x2c, 0x1f, 0xe9, 0xa4, 0x16, 0xb9, 0x5d,
0x8a, 0x33, 0x88, 0xc2, 0xfc, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc6, 0x17, 0xc1,
0xa8, 0xb1, 0xee, 0x2a, 0x81, 0x1c, 0x28, 0xb5, 0xa8, 0x1b, 0x4c, 0x83,
0xd7, 0xc9, 0x8b, 0x5b, 0x0c, 0x27, 0x28, 0x1d, 0x61, 0x02, 0x07, 0xeb,
0xe6, 0x92, 0xc2, 0x96, 0x7f, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc9, 0x0f, 0x33,
0x66, 0x17, 0xb8, 0xe7, 0xf9, 0x83, 0x97, 0x54, 0x13, 0xc9, 0x97, 0xf1,
0x0b, 0x73, 0xeb, 0x26, 0x7f, 0xd8, 0xa1, 0x0c, 0xb9, 0xe3, 0xbd, 0xbf,
0xc6, 0x67, 0xab, 0xdb, 0x8b, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x64, 0x57, 0x5b,
0xd9, 0x12, 0x78, 0x9a, 0x2e, 0x14, 0xad, 0x56, 0xf6, 0x34, 0x1f, 0x52,
0xaf, 0x6b, 0xf8, 0x0c, 0xf9, 0x44, 0x00, 0x78, 0x59, 0x75, 0xe9, 0xf0,
0x4e, 0x2d, 0x64, 0xd7, 0x45, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32,
0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x45, 0xc7, 0xc8,
0xae, 0x75, 0x0a, 0xcf, 0xbb, 0x48, 0xfc, 0x37, 0x52, 0x7d, 0x64, 0x12,
0xdd, 0x64, 0x4d, 0xae, 0xd8, 0x91, 0x3c, 0xcd, 0x8a, 0x24, 0xc9, 0x4d,
0x85, 0x69, 0x67, 0xdf, 0x8e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x02, 0x01, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11,
0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00,
0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00,
0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00,
0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00,
0x00, 0x00, 0x7f, 0x01, 0x04, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41,
0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f,
0x02, 0x01, 0x0c, 0x00, 0xd0, 0x6c, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4,
0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b,
0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00, 0x30,
0x00, 0x30, 0x00, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20,
0x00, 0x55, 0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00, 0x51,
0x00, 0x45, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x20, 0x00, 0x44, 0x00, 0x56,
0x00, 0x44, 0x00, 0x2d, 0x00, 0x52, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x20,
0x00, 0x51, 0x00, 0x4d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x31,
0x00, 0x31, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0,
0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x02,
0x1f, 0x03, 0x12, 0x0a, 0x00, 0x05, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f,
0xff, 0x04, 0x00, 0x4e, 0xac, 0x08, 0x81, 0x11, 0x9f, 0x59, 0x4d, 0x85,
0x0e, 0xe2, 0x1a, 0x52, 0x2c, 0x59, 0xb2, 0xff, 0xff, 0xaa, 0x55, 0x3c,
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2,
0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f,
0x00, 0x6f, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65,
0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0xff,
0xff, 0xaa, 0x55, 0x3d, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x12, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4,
0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b,
0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00, 0x30,
0x00, 0x30, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c,
0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e,
0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6c,
0x00, 0x20, 0x00, 0x53, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c,
0x00, 0x00, 0x00, 0x04, 0x07, 0x14, 0x00, 0xc9, 0xbd, 0xb8, 0x7c, 0xeb,
0xf8, 0x34, 0x4f, 0xaa, 0xea, 0x3e, 0xe4, 0xaf, 0x65, 0x16, 0xa1, 0x04,
0x06, 0x14, 0x00, 0x83, 0xa5, 0x04, 0x7c, 0x3e, 0x9e, 0x1c, 0x4f, 0xad,
0x65, 0xe0, 0x52, 0x68, 0xd0, 0xb4, 0xd1, 0x7f, 0xff, 0x04, 0x00, 0xff,
0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x01, 0xff, 0xaa, 0x55, 0x3f,
0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xe4, 0x07, 0x09, 0x10, 0x10, 0x36, 0x05, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
0x00, 0x47, 0x0c, 0x00, 0x00, 0xcb, 0xb2, 0x19, 0xd7, 0x3a, 0x3d, 0x96,
0x45, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f, 0x64, 0x00, 0x62,
0x00, 0x00, 0x00, 0xa1, 0x59, 0xc0, 0xa5, 0xe4, 0x94, 0xa7, 0x4a, 0x87,
0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72, 0x07, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xeb, 0x05, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77, 0x59,
0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x30,
0x82, 0x05, 0xd7, 0x30, 0x82, 0x03, 0xbf, 0xa0, 0x03, 0x02, 0x01, 0x02,
0x02, 0x0a, 0x61, 0x07, 0x76, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x0b, 0x05, 0x00, 0x30, 0x81, 0x88, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06,
0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e,
0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04,
0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e,
0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x6c, 0x00, 0x00, 0x00, 0x61, 0xdf,
0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03,
0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00,
0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x20, 0x00, 0x55, 0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00,
0x51, 0x00, 0x45, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x20, 0x00, 0x44, 0x00,
0x56, 0x00, 0x44, 0x00, 0x2d, 0x00, 0x52, 0x00, 0x4f, 0x00, 0x4d, 0x00,
0x20, 0x00, 0x51, 0x00, 0x4d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00,
0x31, 0x00, 0x31, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00,
0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00,
0x02, 0x1f, 0x03, 0x12, 0x0a, 0x00, 0x05, 0x00, 0xff, 0xff, 0x00, 0x00,
0x7f, 0xff, 0x04, 0x00, 0x4e, 0xac, 0x08, 0x81, 0x11, 0x9f, 0x59, 0x4d,
0x85, 0x0e, 0xe2, 0x1a, 0x52, 0x2c, 0x59, 0xb2, 0xff, 0xff, 0xaa, 0x55,
0x3c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00,
0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93,
0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00,
0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00,
0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00,
0xff, 0xff, 0xaa, 0x55, 0x3d, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x61, 0xdf,
0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03,
0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00,
0x30, 0x00, 0x30, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x2c, 0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00, 0x49, 0x00,
0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00,
0x6c, 0x00, 0x20, 0x00, 0x53, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00,
0x6c, 0x00, 0x00, 0x00, 0x04, 0x07, 0x14, 0x00, 0xc9, 0xbd, 0xb8, 0x7c,
0xeb, 0xf8, 0x34, 0x4f, 0xaa, 0xea, 0x3e, 0xe4, 0xaf, 0x65, 0x16, 0xa1,
0x04, 0x06, 0x14, 0x00, 0x83, 0xa5, 0x04, 0x7c, 0x3e, 0x9e, 0x1c, 0x4f,
0xad, 0x65, 0xe0, 0x52, 0x68, 0xd0, 0xb4, 0xd1, 0x7f, 0xff, 0x04, 0x00,
0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x30, 0x81, 0x91, 0x31,
0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57,
0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30,
0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d,
0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a,
0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20,
0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31,
0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x4d, 0x69,
0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70,
0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x68, 0x69, 0x72,
0x64, 0x20, 0x50, 0x61, 0x72, 0x74, 0x79, 0x20, 0x4d, 0x61, 0x72, 0x6b,
0x65, 0x74, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74,
0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x36, 0x32, 0x34, 0x32, 0x30,
0x34, 0x31, 0x32, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x30, 0x36, 0x32,
0x34, 0x32, 0x30, 0x35, 0x31, 0x32, 0x39, 0x5a, 0x30, 0x81, 0x80, 0x31,
0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57,
0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30,
0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d,
0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a,
0x01, 0x04, 0x14, 0x00, 0x9b, 0x5a, 0x5a, 0x86, 0x5d, 0xb8, 0x4c, 0x47,
0x84, 0x55, 0x65, 0xd1, 0xbe, 0x84, 0x4b, 0xe2, 0x03, 0x0e, 0x13, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0, 0xbe,
0xf9, 0xd2, 0x11, 0x9a, 0x0c, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d, 0x7f,
0xff, 0x04, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x20,
0x55, 0x45, 0x46, 0x49, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x31,
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0x08, 0x6c,
0x4c, 0xc7, 0x45, 0x09, 0x6a, 0x4b, 0x0c, 0xa4, 0xc0, 0x87, 0x7f, 0x06,
0x75, 0x0c, 0x43, 0x01, 0x54, 0x64, 0xe0, 0x16, 0x7f, 0x07, 0xed, 0x92,
0x7d, 0x0b, 0xb2, 0x73, 0xbf, 0x0c, 0x0a, 0xc6, 0x4a, 0x45, 0x61, 0xa0,
0xc5, 0x16, 0x2d, 0x96, 0xd3, 0xf5, 0x2b, 0xa0, 0xfb, 0x4d, 0x49, 0x9b,
0x41, 0x80, 0x90, 0x3c, 0xb9, 0x54, 0xfd, 0xe6, 0xbc, 0xd1, 0x9d, 0xc4,
0xa4, 0x18, 0x8a, 0x7f, 0x41, 0x8a, 0x5c, 0x59, 0x83, 0x68, 0x32, 0xbb,
0x8c, 0x47, 0xc9, 0xee, 0x71, 0xbc, 0x21, 0x4f, 0x9a, 0x8a, 0x7c, 0xff,
0x44, 0x3f, 0x8d, 0x8f, 0x32, 0xb2, 0x26, 0x48, 0xae, 0x75, 0xb5, 0xee,
0xc9, 0x4c, 0x1e, 0x4a, 0x19, 0x7e, 0xe4, 0x82, 0x9a, 0x1d, 0x78, 0x77,
0x4d, 0x0c, 0xb0, 0xbd, 0xf6, 0x0f, 0xd3, 0x16, 0xd3, 0xbc, 0xfa, 0x2b,
0xa5, 0x51, 0x38, 0x5d, 0xf5, 0xfb, 0xba, 0xdb, 0x78, 0x02, 0xdb, 0xff,
0xec, 0x0a, 0x1b, 0x96, 0xd5, 0x83, 0xb8, 0x19, 0x13, 0xe9, 0xb6, 0xc0,
0x7b, 0x40, 0x7b, 0xe1, 0x1f, 0x28, 0x27, 0xc9, 0xfa, 0xef, 0x56, 0x5e,
0x1c, 0xe6, 0x7e, 0x94, 0x7e, 0xc0, 0xf0, 0x44, 0xb2, 0x79, 0x39, 0xe5,
0xda, 0xb2, 0x62, 0x8b, 0x4d, 0xbf, 0x38, 0x70, 0xe2, 0x68, 0x24, 0x14,
0xc9, 0x33, 0xa4, 0x08, 0x37, 0xd5, 0x58, 0x69, 0x5e, 0xd3, 0x7c, 0xed,
0xc1, 0x04, 0x53, 0x08, 0xe7, 0x4e, 0xb0, 0x2a, 0x87, 0x63, 0x08, 0x61,
0x6f, 0x63, 0x15, 0x59, 0xea, 0xb2, 0x2b, 0x79, 0xd7, 0x0c, 0x61, 0x67,
0x8a, 0x5b, 0xfd, 0x5e, 0xad, 0x87, 0x7f, 0xba, 0x86, 0x67, 0x4f, 0x71,
0x58, 0x12, 0x22, 0x04, 0x22, 0x22, 0xce, 0x8b, 0xef, 0x54, 0x71, 0x00,
0xce, 0x50, 0x35, 0x58, 0x76, 0x95, 0x08, 0xee, 0x6a, 0xb1, 0xa2, 0x01,
0xd5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x76, 0x30, 0x82,
0x01, 0x72, 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82,
0x37, 0x15, 0x01, 0x04, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x23,
0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x02, 0x04,
0x16, 0x04, 0x14, 0xf8, 0xc1, 0x6b, 0xb7, 0x7f, 0x77, 0x53, 0x4a, 0xf3,
0x25, 0x37, 0x1d, 0x4e, 0xa1, 0x26, 0x7b, 0x0f, 0x20, 0x70, 0x80, 0x30,
0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x13, 0xad,
0xbf, 0x43, 0x09, 0xbd, 0x82, 0x70, 0x9c, 0x8c, 0xd5, 0x4f, 0x31, 0x6e,
0xd5, 0x22, 0x98, 0x8a, 0x1b, 0xd4, 0x30, 0x19, 0x06, 0x09, 0x2b, 0x06,
0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c, 0x1e, 0x0a, 0x00,
0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30, 0x0b, 0x06,
0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0f,
0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
0x01, 0x01, 0xff, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
0x30, 0x16, 0x80, 0x14, 0x45, 0x66, 0x52, 0x43, 0xe1, 0x7e, 0x58, 0x11,
0xbf, 0xd6, 0x4e, 0x9e, 0x23, 0x55, 0x08, 0x3b, 0x3a, 0x22, 0x6a, 0xa8,
0x30, 0x5c, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x55, 0x30, 0x53, 0x30,
0x51, 0xa0, 0x4f, 0xa0, 0x4d, 0x86, 0x4b, 0x68, 0x74, 0x74, 0x70, 0x3a,
0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73,
0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f,
0x63, 0x72, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73,
0x2f, 0x4d, 0x69, 0x63, 0x43, 0x6f, 0x72, 0x54, 0x68, 0x69, 0x50, 0x61,
0x72, 0x4d, 0x61, 0x72, 0x52, 0x6f, 0x6f, 0x5f, 0x32, 0x30, 0x31, 0x30,
0x2d, 0x31, 0x30, 0x2d, 0x30, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x60,
0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x54,
0x30, 0x52, 0x30, 0x50, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
0x30, 0x02, 0x86, 0x44, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x63, 0x65, 0x72,
0x74, 0x73, 0x2f, 0x4d, 0x69, 0x63, 0x43, 0x6f, 0x72, 0x54, 0x68, 0x69,
0x50, 0x61, 0x72, 0x4d, 0x61, 0x72, 0x52, 0x6f, 0x6f, 0x5f, 0x32, 0x30,
0x31, 0x30, 0x2d, 0x31, 0x30, 0x2d, 0x30, 0x35, 0x2e, 0x63, 0x72, 0x74,
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x0b, 0x05, 0x00, 0x03, 0x82, 0x02, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x92, 0x00,
0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d,
0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00,
0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00,
0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00,
0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x00, 0x00, 0x00,
0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0,
0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00,
0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00,
0x06, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x82, 0x01,
0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x90, 0x1f, 0x84, 0x7b, 0x8d, 0xbc,
0xeb, 0x97, 0x26, 0x82, 0x6d, 0x88, 0xab, 0x8a, 0xc9, 0x8c, 0x68, 0x70,
0xf9, 0xdf, 0x4b, 0x07, 0xb2, 0x37, 0x83, 0x0b, 0x02, 0xc8, 0x67, 0x68,
0x30, 0x9e, 0xe3, 0xf0, 0xf0, 0x99, 0x4a, 0xb8, 0x59, 0x57, 0xc6, 0x41,
0xf6, 0x38, 0x8b, 0xfe, 0x66, 0x4c, 0x49, 0xe9, 0x37, 0x37, 0x92, 0x2e,
0x98, 0x01, 0x1e, 0x5b, 0x14, 0x50, 0xe6, 0xa8, 0x8d, 0x25, 0x0d, 0xf5,
0x86, 0xe6, 0xab, 0x30, 0xcb, 0x40, 0x16, 0xea, 0x8d, 0x8b, 0x16, 0x86,
0x70, 0x43, 0x37, 0xf2, 0xce, 0xc0, 0x91, 0xdf, 0x71, 0x14, 0x8e, 0x99,
0x0e, 0x89, 0xb6, 0x4c, 0x6d, 0x24, 0x1e, 0x8c, 0xe4, 0x2f, 0x4f, 0x25,
0xd0, 0xba, 0x06, 0xf8, 0xc6, 0xe8, 0x19, 0x18, 0x76, 0x73, 0x1d, 0x81,
0x6d, 0xa8, 0xd8, 0x05, 0xcf, 0x3a, 0xc8, 0x7b, 0x28, 0xc8, 0x36, 0xa3,
0x16, 0x0d, 0x29, 0x8c, 0x99, 0x9a, 0x68, 0xdc, 0xab, 0xc0, 0x4d, 0x8d,
0xbf, 0x5a, 0xbb, 0x2b, 0xa9, 0x39, 0x4b, 0x04, 0x97, 0x1c, 0xf9, 0x36,
0xbb, 0xc5, 0x3a, 0x86, 0x04, 0xae, 0xaf, 0xd4, 0x82, 0x7b, 0xe0, 0xab,
0xde, 0x49, 0x05, 0x68, 0xfc, 0xf6, 0xae, 0x68, 0x1a, 0x6c, 0x90, 0x4d,
0x57, 0x19, 0x3c, 0x64, 0x66, 0x03, 0xf6, 0xc7, 0x52, 0x9b, 0xf7, 0x94,
0xcf, 0x93, 0x6a, 0xa1, 0x68, 0xc9, 0xaa, 0xcf, 0x99, 0x6b, 0xbc, 0xaa,
0x5e, 0x08, 0xe7, 0x39, 0x1c, 0xf7, 0xf8, 0x0f, 0xba, 0x06, 0x7e, 0xf1,
0xcb, 0xe8, 0x76, 0xdd, 0xfe, 0x22, 0xda, 0xad, 0x3a, 0x5e, 0x5b, 0x34,
0xea, 0xb3, 0xc9, 0xe0, 0x4d, 0x04, 0x29, 0x7e, 0xb8, 0x60, 0xb9, 0x05,
0xef, 0xb5, 0xd9, 0x17, 0x58, 0x56, 0x16, 0x60, 0xb9, 0x30, 0x32, 0xf0,
0x36, 0x4a, 0xc3, 0xf2, 0x79, 0x8d, 0x12, 0x40, 0x70, 0xf3, 0x02, 0x03,
0x01, 0x00, 0x01, 0xa3, 0x7b, 0x30, 0x79, 0x30, 0x09, 0x06, 0x03, 0x55,
0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x2c, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d, 0x04, 0x1f, 0x16, 0x1d, 0x4f,
0x70, 0x65, 0x6e, 0x53, 0x53, 0x4c, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72,
0x61, 0x74, 0x65, 0x64, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x65, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
0x16, 0x04, 0x14, 0x3c, 0xe9, 0x60, 0xe3, 0xff, 0x19, 0xa1, 0x0a, 0x7b,
0xa3, 0x42, 0xf4, 0x8d, 0x42, 0x2e, 0xb4, 0xd5, 0x9c, 0x72, 0xec, 0x30,
0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
0x3c, 0xe9, 0x60, 0xe3, 0xff, 0x19, 0xa1, 0x0a, 0x7b, 0xa3, 0x42, 0xf4,
0x8d, 0x42, 0x2e, 0xb4, 0xd5, 0x9c, 0x72, 0xec, 0x30, 0x0d, 0x06, 0x09,
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03,
0x82, 0x01, 0x01, 0x00, 0x5c, 0x4d, 0x92, 0x88, 0xb4, 0x82, 0x5f, 0x1d,
0xad, 0x8b, 0x11, 0xec, 0xdf, 0x06, 0xa6, 0x7a, 0xa5, 0x2b, 0x9f, 0x37,
0x55, 0x0c, 0x8d, 0x6e, 0x05, 0x00, 0xad, 0xb7, 0x0c, 0x41, 0x89, 0x69,
0xcf, 0xd6, 0x65, 0x06, 0x9b, 0x51, 0x78, 0xd2, 0xad, 0xc7, 0xbf, 0x9c,
0xdc, 0x05, 0x73, 0x7f, 0xe7, 0x1e, 0x39, 0x13, 0xb4, 0xea, 0xb6, 0x30,
0x7d, 0x40, 0x75, 0xab, 0x9c, 0x43, 0x0b, 0xdf, 0xb0, 0xc2, 0x1b, 0xbf,
0x30, 0xe0, 0xf4, 0xfe, 0xc0, 0xdb, 0x62, 0x21, 0x98, 0xf6, 0xc5, 0xaf,
0xde, 0x3b, 0x4f, 0x49, 0x0a, 0xe6, 0x1e, 0xf9, 0x86, 0xb0, 0x3f, 0x0d,
0xd6, 0xd4, 0x46, 0x37, 0xdb, 0x54, 0x74, 0x5e, 0xff, 0x11, 0xc2, 0x60,
0xc6, 0x70, 0x58, 0xc5, 0x1c, 0x6f, 0xec, 0xb2, 0xd8, 0x6e, 0x6f, 0xc3,
0xbc, 0x33, 0x87, 0x38, 0xa4, 0xf3, 0x44, 0x64, 0x9c, 0x34, 0x3b, 0x28,
0x94, 0x26, 0x78, 0x27, 0x9f, 0x16, 0x17, 0xe8, 0x3b, 0x69, 0x0a, 0x25,
0xa9, 0x73, 0x36, 0x7e, 0x9e, 0x37, 0x5c, 0xec, 0xe8, 0x3f, 0xdb, 0x91,
0xf9, 0x12, 0xb3, 0x3d, 0xce, 0xe7, 0xdd, 0x15, 0xc3, 0xae, 0x8c, 0x05,
0x20, 0x61, 0x9b, 0x95, 0xde, 0x9b, 0xaf, 0xfa, 0xb1, 0x5c, 0x1c, 0xe5,
0x97, 0xe7, 0xc3, 0x34, 0x11, 0x85, 0xf5, 0x8a, 0x27, 0x26, 0xa4, 0x70,
0x36, 0xec, 0x0c, 0xf6, 0x83, 0x3d, 0x90, 0xf7, 0x36, 0xf3, 0xf9, 0xf3,
0x15, 0xd4, 0x90, 0x62, 0xbe, 0x53, 0xb4, 0xaf, 0xd3, 0x49, 0xaf, 0xef,
0xf4, 0x73, 0xe8, 0x7b, 0x76, 0xe4, 0x44, 0x2a, 0x37, 0xba, 0x81, 0xa4,
0x99, 0x0c, 0x3a, 0x31, 0x24, 0x71, 0xa0, 0xe4, 0xe4, 0xb7, 0x1a, 0xcb,
0x47, 0xe4, 0xaa, 0x22, 0xcf, 0xef, 0x75, 0x61, 0x80, 0xe3, 0x43, 0xb7,
0x48, 0x57, 0x73, 0x11, 0x3d, 0x78, 0x9b, 0x69, 0xa1, 0x59, 0xc0, 0xa5,
0xe4, 0x94, 0xa7, 0x4a, 0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72,
0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x05, 0x00, 0x00,
0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4,
0xe7, 0x8f, 0x78, 0x4b, 0x30, 0x82, 0x05, 0xe8, 0x30, 0x82, 0x03, 0xd0,
0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x0a, 0x61, 0x0a, 0xd1, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
0x08, 0x01, 0x01, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0, 0xbe,
0xf9, 0xd2, 0x11, 0x9a, 0x0c, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d, 0x7f,
0x01, 0x04, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00,
0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c,
0x00, 0xd0, 0x41, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x03, 0x0e, 0x13,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x08, 0x01, 0x01, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0,
0xbe, 0xf9, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd1, 0x00, 0x00, 0x00, 0x61,
0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98,
0x03, 0x2b, 0x8c, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, 0x4f, 0x00, 0x75,
0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02,
0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x03,
0x0e, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0x01, 0x01, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47,
0xc1, 0xe0, 0xbe, 0xf9, 0xd2,
}

View File

@ -10,7 +10,7 @@ replace (
require (
cloud.google.com/go/compute v1.18.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.1.0
github.com/edgelesssys/constellation/v2 v2.6.0
@ -36,7 +36,7 @@ require (
require (
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect

View File

@ -43,12 +43,12 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 h1:rTnT/Jrcm+figWlYz4Ixzt0SJVR2cMC8lvZcimipiEY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0 h1:xGLAFFd9D3iLGxYiUGPdITSzsFmU1K8VtfuUHWAoN7M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 h1:uqM+VoHjVH6zdlkLF2b6O0ZANcHoj3rO0PoQ3jglUJA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2/go.mod h1:twTKAa1E6hLmSDjLhaCkbTMQKc7p/rNLU40rLxGEOCI=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0 h1:leh5DwKv6Ihwi+h60uHtn6UWAxBbZ0q8DwQVMzf61zw=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0.1 h1:H3g2mkmu105ON0c/Gqx3Bm+bzoIijLom8LmV9Gjn7X0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.1.0 h1:Vjq3Uy3JAU1DTxbA+uX6BegIhgO2pyFltbfbmDa9KdI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.1.0/go.mod h1:Q3u+T/qw3Kb1Wf3DFKiFwEZlyaAyPb4yBgWm9wq7yh8=