Skip to content

Commit

Permalink
feat: CLI tests for GCB verification (#251)
Browse files Browse the repository at this point in the history
* update

* update

* update
  • Loading branch information
laurentsimon committed Sep 8, 2022
1 parent 26155fe commit d12dce9
Show file tree
Hide file tree
Showing 15 changed files with 1,198 additions and 76 deletions.
113 changes: 70 additions & 43 deletions cli/slsa-verifier/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"strings"
"testing"
Expand Down Expand Up @@ -703,145 +704,147 @@ func Test_runVerifyGCBArtifactImage(t *testing.T) {
return strings.TrimPrefix(h.String(), "sha256:"), nil
}

builder := "https://cloudbuild.googleapis.com/GoogleHostedWorker@v0.2"
builder := "https://cloudbuild.googleapis.com/GoogleHostedWorker"
tests := []struct {
name string
artifact string
oci bool
provenance string
source string
pbuilderID *string
outBuilderID string
err error
name string
artifact string
artifactDigest map[string]string
remote bool
provenance string
source string
pBuilderID *string
outBuilderID string
err error
// noversion is a special case where we are not testing all builder versions
// for example, testdata for the builder at head in trusted repo workflows
// or testdata from malicious untrusted builders.
// When true, this does not iterate over all builder versions.
noversion bool
// minversion is a special case to test a newly added feature into a builder
minversion string
}{
{
name: "valid main branch default",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
},
{
name: "invalie repo name",
name: "invalid repo name",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/name",
pbuilderID: &builder,
err: serrors.ErrorMismatchSource,
},
{
name: "invalie org name",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/org/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorMismatchSource,
},
{
name: "invalid cloud git",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "gitlab.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorMismatchSource,
},
{
name: "invalid payload digest",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-payload-digest.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid payload builderid",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-payload-builderid.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid summary digest",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-summary-digest.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorMismatchHash,
},
{
name: "invalid text digest",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-text-digest.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorMismatchIntoto,
},
{
name: "invalid text build steps",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-text-steps.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorMismatchIntoto,
},
{
name: "invalid metadata kind",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-metadata-kind.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorInvalidFormat,
},
{
name: "invalid metadata resourceUri sha256",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-metadata-urisha256.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorMismatchHash,
},
{
name: "oci valid with tag",
// Image us-west2-docker.pkg.dev/gosst-scare-sandbox/quickstart-docker-repo/quickstart-image:v14@sha256:1a033b002f89ed2b8ea733162497fb70f1a4049a7f8602d6a33682b4ad9921fd
// re-tagged and pushed to docker hub. This image is public.
artifact: "laurentsimon/slsa-gcb-v0.2:test@sha256:1a033b002f89ed2b8ea733162497fb70f1a4049a7f8602d6a33682b4ad9921fd",
oci: true,
// Image re-tagged and pushed to docker hub. This image is public.
artifact: "laurentsimon/slsa-gcb-%s:test",
artifactDigest: map[string]string{
"v0.2": "1a033b002f89ed2b8ea733162497fb70f1a4049a7f8602d6a33682b4ad9921fd",
"v0.3": "f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7",
},
remote: true,
source: "github.com/laurentsimon/gcb-tests",
provenance: "gcloud-container-github.json",
pbuilderID: &builder,
},
{
name: "oci valid no tag",
artifact: "laurentsimon/slsa-gcb-v0.2@sha256:1a033b002f89ed2b8ea733162497fb70f1a4049a7f8602d6a33682b4ad9921fd",
oci: true,
name: "oci mismatch digest",
artifact: "index.docker.io/laurentsimon/scorecard",
artifactDigest: map[string]string{
"v0.2": "d794817bdf9c7e5ec34758beb90a18113c7dfbd737e760cabf8dd923d49e96f4",
"v0.3": "d794817bdf9c7e5ec34758beb90a18113c7dfbd737e760cabf8dd923d49e96f4",
},
remote: true,
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorMismatchHash,
},
{
name: "oci valid no tag",
artifact: "laurentsimon/slsa-gcb-%s",
artifactDigest: map[string]string{
"v0.2": "1a033b002f89ed2b8ea733162497fb70f1a4049a7f8602d6a33682b4ad9921fd",
"v0.3": "f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7",
},
remote: true,
source: "github.com/laurentsimon/gcb-tests",
provenance: "gcloud-container-github.json",
pbuilderID: &builder,
},
// No version.
{
name: "oci is mutable",
artifact: "index.docker.io/laurentsimon/scorecard",
oci: true,
noversion: true,
remote: true,
source: "github.com/laurentsimon/gcb-tests",
provenance: "gcloud-container-github.json",
pbuilderID: &builder,
pBuilderID: pString(builder + "@v0.2"),
err: serrors.ErrorMutableImage,
},
{
name: "oci mismatch digest",
artifact: "index.docker.io/laurentsimon/scorecard@sha256:d794817bdf9c7e5ec34758beb90a18113c7dfbd737e760cabf8dd923d49e96f4",
oci: true,
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
pbuilderID: &builder,
err: serrors.ErrorMismatchHash,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
Expand All @@ -854,18 +857,42 @@ func Test_runVerifyGCBArtifactImage(t *testing.T) {
}

for _, v := range checkVersions {
semver := path.Base(v)
builderID := pString(builder + "@" + semver)
provenance := filepath.Clean(filepath.Join(TEST_DIR, v, tt.provenance))
image := tt.artifact
var fn verify.ComputeDigestFn
if !tt.oci {

// If builder ID is set, use it.
if tt.pBuilderID != nil {
if !tt.noversion {
panic("builderID set but not noversion option")
}
builderID = tt.pBuilderID
}

// Select the right image according to the builder version we are testing.
if strings.Contains(image, `%s`) {
image = fmt.Sprintf(image, semver)
}
// Add the sha256 digest to the image name, if provided.
if len(tt.artifactDigest) > 0 {
digest, ok := tt.artifactDigest[semver]
if !ok {
panic(fmt.Sprintf("%s not present in artifactDigest %v", semver, tt.artifactDigest))
}
image = fmt.Sprintf("%s@sha256:%s", image, digest)
}
// If it is a local image, change the digest computation.
if !tt.remote {
image = filepath.Clean(filepath.Join(TEST_DIR, v, image))
fn = localDigestComputeFn
}

cmd := verify.VerifyImageCommand{
SourceURI: tt.source,
SourceBranch: nil,
BuilderID: tt.pbuilderID,
BuilderID: builderID,
SourceTag: nil,
SourceVersionTag: nil,
DigestFn: fn,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"image_summary": {
"digest": "sha256:f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7",
"fully_qualified_digest": "us-west2-docker.pkg.dev/gosst-scare-sandbox/quickstart-docker-repo/quickstart-image@sha256:f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7",
"registry": "us-west2-docker.pkg.dev",
"repository": "quickstart-docker-repo"
},
"provenance_summary": {
"provenance": [
{
"build": {
"intotoStatement": {
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.1",
"slsaProvenance": {
"builder": {
"id": "https://cloudbuild.googleapis.com/GoogleHostedWorker@v0.3"
},
"materials": [
{
"digest": {
"sha1": "01ce393d04eb6df2a7b2b3e95d4126e687afb7ae"
},
"uri": "https://github.com/laurentsimon/gcb-tests"
}
],
"metadata": {
"buildFinishedOn": "2022-09-06T17:54:22.169342Z",
"buildInvocationId": "11f6c682-3451-4f72-ac2a-8e386eab66af",
"buildStartedOn": "2022-09-06T17:54:10.226833361Z"
},
"recipe": {
"arguments": {
"@type": "type.googleapis.com/google.devtools.cloudbuild.v1.Build",
"id": "11f6c682-3451-4f72-ac2a-8e386eab66af",
"options": {
"dynamicSubstitutions": true,
"logging": "LEGACY",
"pool": {},
"requestedVerifyOption": "VERIFIED",
"substitutionOption": "ALLOW_LOOSE"
},
"sourceProvenance": {},
"steps": [
{
"args": [
"build",
"-t",
"us-west2-docker.pkg.dev/gosst-scare-sandbox/quickstart-docker-repo/quickstart-image:v39",
"."
],
"name": "gcr.io/cloud-builders/docker",
"pullTiming": {
"endTime": "2022-09-06T17:54:13.240503649Z",
"startTime": "2022-09-06T17:54:13.237138056Z"
},
"status": "SUCCESS",
"timing": {
"endTime": "2022-09-06T17:54:20.155242044Z",
"startTime": "2022-09-06T17:54:13.237138056Z"
}
}
],
"substitutions": {
"COMMIT_SHA": "01ce393d04eb6df2a7b2b3e95d4126e687afb7ae",
"REF_NAME": "v39",
"REPO_NAME": "gcb-tests",
"REVISION_ID": "01ce393d04eb6df2a7b2b3e95d4126e687afb7ae",
"SHORT_SHA": "01ce393",
"TAG_NAME": "v39",
"TRIGGER_BUILD_CONFIG_PATH": "cloudbuild.yaml",
"TRIGGER_NAME": "Tag"
}
},
"entryPoint": "cloudbuild.yaml",
"type": "https://cloudbuild.googleapis.com/CloudBuildYaml@v0.1"
}
},
"subject": [
{
"digest": {
"sha256": "f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7"
},
"name": "https://us-west2-docker.pkg.dev/gosst-scare-sandbox/quickstart-docker-repo/quickstart-image:v39"
}
]
}
},
"createTime": "2022-09-06T17:54:23.761540Z",
"envelope": {
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZSI6eyJidWlsZGVyIjp7ImlkIjoiaHR0cHM6Ly9jbG91ZGJ1aWxkLmdvb2dsZWFwaXMuY29tL0dvb2dsZUhvc3RlZFdvcmtlckB2MC4zIn0sIm1hdGVyaWFscyI6W3siZGlnZXN0Ijp7InNoYTEiOiIwMWNlMzkzZDA0ZWI2ZGYyYTdiMmIzZTk1ZDQxMjZlNjg3YWZiN2FlIn0sInVyaSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9sYXVyZW50c2ltb24vZ2NiLXRlc3RzIn1dLCJtZXRhZGF0YSI6eyJidWlsZEZpbmlzaGVkT24iOiIyMDIyLTA5LTA2VDE3OjU0OjIyLjE2OTM0MloiLCJidWlsZEludm9jYXRpb25JZCI6IjExZjZjNjgyLTM0NTEtNGY3Mi1hYzJhLThlMzg2ZWFiNjZhZiIsImJ1aWxkU3RhcnRlZE9uIjoiMjAyMi0wOS0wNlQxNzo1NDoxMC4yMjY4MzMzNjFaIn0sInJlY2lwZSI6eyJhcmd1bWVudHMiOnsiQHR5cGUiOiJ0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5kZXZ0b29scy5jbG91ZGJ1aWxkLnYxLkJ1aWxkIiwiaWQiOiIxMWY2YzY4Mi0zNDUxLTRmNzItYWMyYS04ZTM4NmVhYjY2YWYiLCJvcHRpb25zIjp7ImR5bmFtaWNTdWJzdGl0dXRpb25zIjp0cnVlLCJsb2dnaW5nIjoiTEVHQUNZIiwicG9vbCI6e30sInJlcXVlc3RlZFZlcmlmeU9wdGlvbiI6IlZFUklGSUVEIiwic3Vic3RpdHV0aW9uT3B0aW9uIjoiQUxMT1dfTE9PU0UifSwic291cmNlUHJvdmVuYW5jZSI6e30sInN0ZXBzIjpbeyJhcmdzIjpbImJ1aWxkIiwiLXQiLCJ1cy13ZXN0Mi1kb2NrZXIucGtnLmRldi9nb3NzdC1zY2FyZS1zYW5kYm94L3F1aWNrc3RhcnQtZG9ja2VyLXJlcG8vcXVpY2tzdGFydC1pbWFnZTp2MzkiLCIuIl0sIm5hbWUiOiJnY3IuaW8vY2xvdWQtYnVpbGRlcnMvZG9ja2VyIiwicHVsbFRpbWluZyI6eyJlbmRUaW1lIjoiMjAyMi0wOS0wNlQxNzo1NDoxMy4yNDA1MDM2NDlaIiwic3RhcnRUaW1lIjoiMjAyMi0wOS0wNlQxNzo1NDoxMy4yMzcxMzgwNTZaIn0sInN0YXR1cyI6IlNVQ0NFU1MiLCJ0aW1pbmciOnsiZW5kVGltZSI6IjIwMjItMDktMDZUMTc6NTQ6MjAuMTU1MjQyMDQ0WiIsInN0YXJ0VGltZSI6IjIwMjItMDktMDZUMTc6NTQ6MTMuMjM3MTM4MDU2WiJ9fV0sInN1YnN0aXR1dGlvbnMiOnsiQ09NTUlUX1NIQSI6IjAxY2UzOTNkMDRlYjZkZjJhN2IyYjNlOTVkNDEyNmU2ODdhZmI3YWUiLCJSRUZfTkFNRSI6InYzOSIsIlJFUE9fTkFNRSI6ImdjYi10ZXN0cyIsIlJFVklTSU9OX0lEIjoiMDFjZTM5M2QwNGViNmRmMmE3YjJiM2U5NWQ0MTI2ZTY4N2FmYjdhZSIsIlNIT1JUX1NIQSI6IjAxY2UzOTMiLCJUQUdfTkFNRSI6InYzOSIsIlRSSUdHRVJfQlVJTERfQ09ORklHX1BBVEgiOiJjbG91ZGJ1aWxkLnlhbWwiLCJUUklHR0VSX05BTUUiOiJUYWcifX0sImVudHJ5UG9pbnQiOiJjbG91ZGJ1aWxkLnlhbWwiLCJ0eXBlIjoiaHR0cHM6Ly9jbG91ZGJ1aWxkLmdvb2dsZWFwaXMuY29tL0Nsb3VkQnVpbGRZYW1sQHYwLjEifX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMSIsInNsc2FQcm92ZW5hbmNlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2Nsb3VkYnVpbGQuZ29vZ2xlYXBpcy5jb20vR29vZ2xlSG9zdGVkV29ya2VyQHYwLjMifSwibWF0ZXJpYWxzIjpbeyJkaWdlc3QiOnsic2hhMSI6IjAxY2UzOTNkMDRlYjZkZjJhN2IyYjNlOTVkNDEyNmU2ODdhZmI3YWUifSwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2xhdXJlbnRzaW1vbi9nY2ItdGVzdHMifV0sIm1ldGFkYXRhIjp7ImJ1aWxkRmluaXNoZWRPbiI6IjIwMjItMDktMDZUMTc6NTQ6MjIuMTY5MzQyWiIsImJ1aWxkSW52b2NhdGlvbklkIjoiMTFmNmM2ODItMzQ1MS00ZjcyLWFjMmEtOGUzODZlYWI2NmFmIiwiYnVpbGRTdGFydGVkT24iOiIyMDIyLTA5LTA2VDE3OjU0OjEwLjIyNjgzMzM2MVoifSwicmVjaXBlIjp7ImFyZ3VtZW50cyI6eyJAdHlwZSI6InR5cGUuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLmRldnRvb2xzLmNsb3VkYnVpbGQudjEuQnVpbGQiLCJpZCI6IjExZjZjNjgyLTM0NTEtNGY3Mi1hYzJhLThlMzg2ZWFiNjZhZiIsIm9wdGlvbnMiOnsiZHluYW1pY1N1YnN0aXR1dGlvbnMiOnRydWUsImxvZ2dpbmciOiJMRUdBQ1kiLCJwb29sIjp7fSwicmVxdWVzdGVkVmVyaWZ5T3B0aW9uIjoiVkVSSUZJRUQiLCJzdWJzdGl0dXRpb25PcHRpb24iOiJBTExPV19MT09TRSJ9LCJzb3VyY2VQcm92ZW5hbmNlIjp7fSwic3RlcHMiOlt7ImFyZ3MiOlsiYnVpbGQiLCItdCIsInVzLXdlc3QyLWRvY2tlci5wa2cuZGV2L2dvc3N0LXNjYXJlLXNhbmRib3gvcXVpY2tzdGFydC1kb2NrZXItcmVwby9xdWlja3N0YXJ0LWltYWdlOnYzOSIsIi4iXSwibmFtZSI6Imdjci5pby9jbG91ZC1idWlsZGVycy9kb2NrZXIiLCJwdWxsVGltaW5nIjp7ImVuZFRpbWUiOiIyMDIyLTA5LTA2VDE3OjU0OjEzLjI0MDUwMzY0OVoiLCJzdGFydFRpbWUiOiIyMDIyLTA5LTA2VDE3OjU0OjEzLjIzNzEzODA1NloifSwic3RhdHVzIjoiU1VDQ0VTUyIsInRpbWluZyI6eyJlbmRUaW1lIjoiMjAyMi0wOS0wNlQxNzo1NDoyMC4xNTUyNDIwNDRaIiwic3RhcnRUaW1lIjoiMjAyMi0wOS0wNlQxNzo1NDoxMy4yMzcxMzgwNTZaIn19XSwic3Vic3RpdHV0aW9ucyI6eyJDT01NSVRfU0hBIjoiMDFjZTM5M2QwNGViNmRmMmE3YjJiM2U5NWQ0MTI2ZTY4N2FmYjdhZSIsIlJFRl9OQU1FIjoidjM5IiwiUkVQT19OQU1FIjoiZ2NiLXRlc3RzIiwiUkVWSVNJT05fSUQiOiIwMWNlMzkzZDA0ZWI2ZGYyYTdiMmIzZTk1ZDQxMjZlNjg3YWZiN2FlIiwiU0hPUlRfU0hBIjoiMDFjZTM5MyIsIlRBR19OQU1FIjoidjM5IiwiVFJJR0dFUl9CVUlMRF9DT05GSUdfUEFUSCI6ImNsb3VkYnVpbGQueWFtbCIsIlRSSUdHRVJfTkFNRSI6IlRhZyJ9fSwiZW50cnlQb2ludCI6ImNsb3VkYnVpbGQueWFtbCIsInR5cGUiOiJodHRwczovL2Nsb3VkYnVpbGQuZ29vZ2xlYXBpcy5jb20vQ2xvdWRCdWlsZFlhbWxAdjAuMSJ9fSwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImY0NzJjYTRiNjg4OThjOTUxYWMzYjQ3NmNiYTkxOWQwZDU2ZmNhNGNlZDYzMWZhYmNlYWQ1MWU0YjJiNjkwZTcifSwibmFtZSI6Imh0dHBzOi8vdXMtd2VzdDItZG9ja2VyLnBrZy5kZXYvZ29zc3Qtc2NhcmUtc2FuZGJveC9xdWlja3N0YXJ0LWRvY2tlci1yZXBvL3F1aWNrc3RhcnQtaW1hZ2U6djM5In1dfQ==",
"payloadType": "application/vnd.in-toto+json",
"signatures": [
{
"keyid": "projects/verified-builder/locations/us-west2/keyRings/attestor/cryptoKeys/builtByGCB/cryptoKeyVersions/1",
"sig": "MEQCID2DrzUtVIv55nSl0FdoYdaaayxrjOOF2i35yadBIvFdAiAZhG4k1dC2RmSbIBVctPQ10bTzeN4XKU7Vm9E5oMJAJQ=="
}
]
},
"kind": "BUILD",
"name": "projects/gosst-scare-sandbox/occurrences/768ee56d-2064-4ed9-9cd4-8232df1a1792",
"noteName": "projects/verified-builder/notes/intoto_11f6c682-3451-4f72-ac2a-8e386eab66af",
"resourceUri": "https://us-west2-docker.pkg.dev/gosst-scare-sandbox/quickstart-docker-repo/quickstart-image@sha256:f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7",
"updateTime": "2022-09-06T17:54:23.761540Z"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 2034,
"digest": "sha256:738fc12a4294f732405a61e2416e1f383023da550b56536ecd73addd962a0eb7"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 22486039,
"digest": "sha256:f17d81b4b692f7e0d6c1176c86b81d9f2cb5ac5349703adca51c61debcfe413c"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 159,
"digest": "sha256:dc321114710ef96093295ef8b489c8c3ba5be1ecf1e92ae1e229ed23df91e37f"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 159,
"digest": "sha256:dc321114710ef96093295ef8b489c8c3ba5be1ecf1e92ae1e229ed23df91e37f"
}
]
}
Loading

0 comments on commit d12dce9

Please sign in to comment.