Repository: osbuild/osbuild-composer
Pull request #2837 (2022-07-16 17:10:16)

github: fix the coverity scan action
It was failing on:

vendor/github.com/proglottis/gpgme/data.go:4:11: fatal error: gpgme.h: No such file or directory

Let's install this package before running the check, I verified that this
fix works locally.

@atodorov This check was failing for 16 days so I presume that nobody checks it. Is it even useful if nobody checks its result?

Signed-off-by: Ondřej Budai <ondrej@budai.cz>


This pull request includes:

- [ ] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`

Information regarding our GitLab pipeline (Schutzbot):

CI will not be ran automatically if WIP label is applied or the PR is in DRAFT state, instead only
a link will be provided to the pipeline which can then be triggered manually if desired. To run the
CI automatically either switch the PR to ready or apply WIP+test label.

Outside contributors need manual approval from one of the osbuild-composer maintainers.

Schutzbot will only be triggered if all Tests jobs in GitHub workflow succeed.
-->



Patch:
diff --git a/.github/workflows/coverity_scan.yml b/.github/workflows/coverity_scan.yml
index 81f2fb4a4..af8acf40e 100644
--- a/.github/workflows/coverity_scan.yml
+++ b/.github/workflows/coverity_scan.yml
@@ -20,7 +20,7 @@ jobs:
           COVERITY_SCAN_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }}
         run: |
           sudo apt-get update
-          sudo apt-get install -y libkrb5-dev
+          sudo apt-get install -y libkrb5-dev libgpgme-dev
 
           echo "Downloading coverity scan package."
           curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 \
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 527b93c29..e8031ccb6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,21 +8,18 @@ stages:
 
 .base:
   before_script:
-    - schutzbot/ci_details.sh > ci-details-before-run
+    - schutzbot/ci_details.sh > ci-details-before-run.txt
     - cat schutzbot/team_ssh_keys.txt | tee -a ~/.ssh/authorized_keys > /dev/null
   after_script:
-    - schutzbot/ci_details.sh > ci-details-after-run || true
+    - schutzbot/ci_details.sh > ci-details-after-run.txt || true
     - schutzbot/update_github_status.sh update || true
     - schutzbot/save_journal.sh || true
+    - schutzbot/upload_artifacts.sh
   interruptible: true
   tags:
     - terraform
   artifacts:
     paths:
-      - journal-log.gpg
-      - ci-artifacts
-      - ci-details-before-run
-      - ci-details-after-run
       - "*.repo"
       - COMPOSER_NVR
     when: always
@@ -446,17 +443,7 @@ aws.sh:
   extends: .integration
   variables:
     SCRIPT: aws.sh
-  artifacts:
-    paths:
-      - cloud-image-val/report.html
-      - journal-log.gpg
-      - ci-artifacts
-      - ci-details-before-run
-      - ci-details-after-run
-      - "*.repo"
-      - COMPOSER_NVR
-    when: always
-
+    
 azure.sh:
   extends: .integration
   variables:
@@ -595,18 +582,6 @@ Upgrade:
     - /usr/libexec/tests/osbuild-composer/upgrade8to9.sh
   variables:
     RUNNER: rhos-01/rhel-9.1-nightly-x86_64
-  artifacts:
-    paths:
-      - journal-log.gpg
-      - ci-artifacts
-      - install_console.log
-      - upgrade_console.log
-      - leapp-preupgrade.log
-      - leapp-upgrade.log
-      - leapp-report.txt
-      - osbuild-upgrade8to9.log
-      - osbuild-upgrade8to9.json
-    when: always
 
 NIGHTLY_FAIL:
   stage: finish
diff --git a/Schutzfile b/Schutzfile
index df3e6d204..20d75b915 100644
--- a/Schutzfile
+++ b/Schutzfile
@@ -46,14 +46,14 @@
           {
             "title": "updates",
             "name": "updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-x86_64-updates-released-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-x86_64-updates-released-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "updates",
             "name": "updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-aarch64-updates-released-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-aarch64-updates-released-20220715"
           }
         ]
       },
@@ -63,14 +63,14 @@
           {
             "title": "updates-modular",
             "name": "updates-modular",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-x86_64-updates-released-modular-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-x86_64-updates-released-modular-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "updates-modular",
             "name": "updates-modular",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-aarch64-updates-released-modular-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f35/f35-aarch64-updates-released-modular-20220715"
           }
         ]
       }
@@ -123,14 +123,14 @@
           {
             "title": "updates",
             "name": "updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-x86_64-updates-released-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-x86_64-updates-released-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "updates",
             "name": "updates",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-aarch64-updates-released-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-aarch64-updates-released-20220715"
           }
         ]
       },
@@ -140,14 +140,14 @@
           {
             "title": "updates-modular",
             "name": "updates-modular",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-x86_64-updates-released-modular-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-x86_64-updates-released-modular-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "updates-modular",
             "name": "updates-modular",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-aarch64-updates-released-modular-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f36/f36-aarch64-updates-released-modular-20220715"
           }
         ]
       }
@@ -180,34 +180,34 @@
           {
             "title": "RHEL-8-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-baseos-n8.7-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-baseos-n8.7-20220715"
           },
           {
             "title": "RHEL-8-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-appstream-n8.7-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-appstream-n8.7-20220715"
           },
           {
             "title": "RHEL-8-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-crb-n8.7-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-crb-n8.7-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "RHEL-8-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-aarch64-baseos-n8.7-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-aarch64-baseos-n8.7-20220715"
           },
           {
             "title": "RHEL-8-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-aarch64-appstream-n8.7-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-aarch64-appstream-n8.7-20220715"
           },
           {
             "title": "RHEL-8-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-aarch64-crb-n8.7-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-aarch64-crb-n8.7-20220715"
           }
         ]
       }
@@ -233,34 +233,34 @@
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-baseos-n9.1-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-baseos-n9.1-20220715"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-appstream-n9.1-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-appstream-n9.1-20220715"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-crb-n9.1-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-crb-n9.1-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-BaseOS",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-baseos-n9.1-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-baseos-n9.1-20220715"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-AppStream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-appstream-n9.1-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-appstream-n9.1-20220715"
           },
           {
             "title": "RHEL-9-RPMREPO-NIGHTLY-CRB",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-crb-n9.1-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-crb-n9.1-20220715"
           }
         ]
       }
@@ -288,34 +288,34 @@
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-baseos-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-baseos-20220715"
           },
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-appstream-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-appstream-20220715"
           },
           {
             "title": "crb",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-crb-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-x86_64-crb-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-baseos-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-baseos-20220715"
           },
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-appstream-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-appstream-20220715"
           },
           {
             "title": "crb",
             "name": "crb",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-crb-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/cs9-aarch64-crb-20220715"
           }
         ]
       }
@@ -329,14 +329,14 @@
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-x86_64-baseos-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-x86_64-baseos-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "baseos",
             "name": "baseos",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-aarch64-baseos-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-aarch64-baseos-20220715"
           }
         ]
       },
@@ -346,14 +346,14 @@
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-x86_64-appstream-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-x86_64-appstream-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "appstream",
             "name": "appstream",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-aarch64-appstream-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-aarch64-appstream-20220715"
           }
         ]
       },
@@ -363,14 +363,14 @@
           {
             "title": "powertools",
             "name": "powertools",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-x86_64-powertools-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-x86_64-powertools-20220715"
           }
         ],
         "aarch64": [
           {
             "title": "powertools",
             "name": "powertoosl",
-            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-aarch64-powertools-20220701"
+            "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/cs8-aarch64-powertools-20220715"
           }
         ]
       }
diff --git a/internal/blueprint/blueprint.go b/internal/blueprint/blueprint.go
index 31f9c01f7..f90154b09 100644
--- a/internal/blueprint/blueprint.go
+++ b/internal/blueprint/blueprint.go
@@ -5,6 +5,8 @@ import (
 	"encoding/json"
 	"fmt"
 
+	"github.com/osbuild/osbuild-composer/internal/crypt"
+
 	"github.com/coreos/go-semver/semver"
 )
 
@@ -74,6 +76,12 @@ func (b *Blueprint) Initialize() error {
 	if err != nil {
 		return fmt.Errorf("Invalid 'version', must use Semantic Versioning: %s", err.Error())
 	}
+
+	err = b.CryptPasswords()
+	if err != nil {
+		return fmt.Errorf("Error hashing passwords: %s", err.Error())
+	}
+
 	return nil
 }
 
@@ -126,3 +134,36 @@ func (p Package) ToNameVersion() string {
 
 	return p.Name + "-" + p.Version
 }
+
+// CryptPasswords ensures that all blueprint passwords are hashed
+func (b *Blueprint) CryptPasswords() error {
+	if b.Customizations == nil {
+		return nil
+	}
+
+	// Any passwords for users?
+	for i := range b.Customizations.User {
+		// Missing or empty password
+		if b.Customizations.User[i].Password == nil {
+			continue
+		}
+
+		// Prevent empty password from being hashed
+		if len(*b.Customizations.User[i].Password) == 0 {
+			b.Customizations.User[i].Password = nil
+			continue
+		}
+
+		if !crypt.PasswordIsCrypted(*b.Customizations.User[i].Password) {
+			pw, err := crypt.CryptSHA512(*b.Customizations.User[i].Password)
+			if err != nil {
+				return err
+			}
+
+			// Replace the password with the
+			b.Customizations.User[i].Password = &pw
+		}
+	}
+
+	return nil
+}
diff --git a/internal/osbuild/users_stage.go b/internal/osbuild/users_stage.go
index 6a1a303b7..fab65388e 100644
--- a/internal/osbuild/users_stage.go
+++ b/internal/osbuild/users_stage.go
@@ -36,6 +36,12 @@ func NewUsersStageOptions(userCustomizations []blueprint.UserCustomization, omit
 
 	users := make(map[string]UsersStageOptionsUser, len(userCustomizations))
 	for _, uc := range userCustomizations {
+		// Don't hash empty passwords, set to nil to lock account
+		if uc.Password != nil && len(*uc.Password) == 0 {
+			uc.Password = nil
+		}
+
+		// Hash non-empty un-hashed passwords
 		if uc.Password != nil && !crypt.PasswordIsCrypted(*uc.Password) {
 			cryptedPassword, err := crypt.CryptSHA512(*uc.Password)
 			if err != nil {
diff --git a/schutzbot/ci_details.sh b/schutzbot/ci_details.sh
index 46d2fafc5..bb9e7aed8 100755
--- a/schutzbot/ci_details.sh
+++ b/schutzbot/ci_details.sh
@@ -34,7 +34,7 @@ EOF
 echo -e "\033[0m"
 
 echo "List of system repositories:"
-yum repolist -v
+sudo yum repolist -v
 
 echo "------------------------------------------------------------------------------"
 
diff --git a/schutzbot/save_journal.sh b/schutzbot/save_journal.sh
index 79a9ebabc..ab359c473 100755
--- a/schutzbot/save_journal.sh
+++ b/schutzbot/save_journal.sh
@@ -3,7 +3,5 @@
 # use tee, otherwise shellcheck complains
 sudo journalctl --boot | tee journal-log >/dev/null
 
-# As it might contain sensitive information and is important for debugging
-# purposes, encrypt journal-log with a symmetric passphrase.
-gpg --batch --yes --passphrase "$GPG_SYMMETRIC_PASSPHRASE" -o journal-log.gpg --symmetric journal-log
-rm journal-log
+# copy journal to artifacts folder which is then uploaded to secure S3 location
+cp journal-log "${ARTIFACTS:-/tmp/artifacts}"
diff --git a/schutzbot/upload_artifacts.sh b/schutzbot/upload_artifacts.sh
new file mode 100755
index 000000000..8d9d43d17
--- /dev/null
+++ b/schutzbot/upload_artifacts.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# This script uploads all files from ARTIFACTS folder to S3
+
+S3_URL="s3://image-builder-ci-artifacts/osbuild-composer/$CI_COMMIT_BRANCH/$CI_JOB_ID/"
+BROWSER_URL="https://s3.console.aws.amazon.com/s3/buckets/image-builder-ci-artifacts?region=us-east-1&prefix=osbuild-composer/$CI_COMMIT_BRANCH/$CI_JOB_ID/&showversions=false"
+ARTIFACTS=${ARTIFACTS:-/tmp/artifacts}
+
+# Colorful output.
+function greenprint {
+  echo -e "\033[1;32m[$(date -Isecond)] ${1}\033[0m"
+}
+
+sudo dnf -y install s3cmd
+greenprint "Job artifacts will be uploaded to: $S3_URL"
+
+AWS_SECRET_ACCESS_KEY="$V2_AWS_SECRET_ACCESS_KEY" \
+AWS_ACCESS_KEY_ID="$V2_AWS_ACCESS_KEY_ID" \
+s3cmd --acl-private put "$ARTIFACTS"/* "$S3_URL"
+
+greenprint "Please login to 438669297788 AWS account and visit $BROWSER_URL to access job artifacts."
diff --git a/tools/provision.sh b/tools/provision.sh
index b5c69f822..680d860e9 100755
--- a/tools/provision.sh
+++ b/tools/provision.sh
@@ -3,6 +3,10 @@ set -euxo pipefail
 
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 
+# create artifacts folder
+ARTIFACTS="${ARTIFACTS:=/tmp/artifacts}"
+mkdir -p "${ARTIFACTS}"
+
 # koji and ansible are not in RHEL repositories. Depending on them in the spec
 # file breaks RHEL gating (see OSCI-1541). Therefore, we need to enable epel
 # and install koji and ansible here.


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ddd1ff396..3a090dc26 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -58,7 +58,7 @@ jobs:
           - 5432:5432
     steps:
     - uses: actions/checkout@v2
-    - uses: actions/setup-go@v2
+    - uses: actions/setup-go@v3
       with:
         go-version: 1.16
     - env:
diff --git a/internal/blueprint/blueprint_test.go b/internal/blueprint/blueprint_test.go
index 719ae3ffa..9063718fd 100644
--- a/internal/blueprint/blueprint_test.go
+++ b/internal/blueprint/blueprint_test.go
@@ -2,6 +2,7 @@ package blueprint
 
 import (
 	"encoding/json"
+	"strings"
 	"testing"
 
 	"github.com/BurntSushi/toml"
@@ -213,3 +214,38 @@ func TestKernelNameCustomization(t *testing.T) {
 		}
 	}
 }
+
+// TestBlueprintPasswords check to make sure all passwords are hashed
+func TestBlueprintPasswords(t *testing.T) {
+	blueprint := `
+name = "test"
+description = "Test"
+version = "0.0.0"
+
+[[customizations.user]]
+name = "bart"
+password = "nobodysawmedoit"
+
+[[customizations.user]]
+name = "lisa"
+password = "$6$RWdHzrPfoM6BMuIP$gKYlBXQuJgP.G2j2twbOyxYjFDPUQw8Jp.gWe1WD/obX0RMyfgw5vt.Mn/tLLX4mQjaklSiIzoAW3HrVQRg4Q."
+
+[[customizations.user]]
+name = "maggie"
+password = ""
+`
+
+	var bp Blueprint
+	err := toml.Unmarshal([]byte(blueprint), &bp)
+	require.Nil(t, err)
+	require.Nil(t, bp.Initialize())
+
+	// Note: User entries are in the same order as the toml
+	users := bp.Customizations.GetUsers()
+	assert.Equal(t, "bart", users[0].Name)
+	assert.True(t, strings.HasPrefix(*users[0].Password, "$6$"))
+	assert.Equal(t, "lisa", users[1].Name)
+	assert.Equal(t, "$6$RWdHzrPfoM6BMuIP$gKYlBXQuJgP.G2j2twbOyxYjFDPUQw8Jp.gWe1WD/obX0RMyfgw5vt.Mn/tLLX4mQjaklSiIzoAW3HrVQRg4Q.", *users[1].Password)
+	assert.Equal(t, "maggie", users[2].Name)
+	assert.Nil(t, users[2].Password)
+}
diff --git a/internal/osbuild/users_stage_test.go b/internal/osbuild/users_stage_test.go
index 3db69c543..ec858bc73 100644
--- a/internal/osbuild/users_stage_test.go
+++ b/internal/osbuild/users_stage_test.go
@@ -1,9 +1,13 @@
 package osbuild
 
 import (
+	"strings"
 	"testing"
 
+	"github.com/osbuild/osbuild-composer/internal/blueprint"
+
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestNewUsersStage(t *testing.T) {
@@ -14,3 +18,43 @@ func TestNewUsersStage(t *testing.T) {
 	actualStage := NewUsersStage(&UsersStageOptions{})
 	assert.Equal(t, expectedStage, actualStage)
 }
+
+func TestNewUsersStageOptionsPassword(t *testing.T) {
+	Pass := "testpass"
+	EmptyPass := ""
+	CryptPass := "$6$RWdHzrPfoM6BMuIP$gKYlBXQuJgP.G2j2twbOyxYjFDPUQw8Jp.gWe1WD/obX0RMyfgw5vt.Mn/tLLX4mQjaklSiIzoAW3HrVQRg4Q." // #nosec G101
+
+	users := []blueprint.UserCustomization{
+		blueprint.UserCustomization{
+			Name:     "bart",
+			Password: &Pass,
+		},
+		blueprint.UserCustomization{
+			Name:     "lisa",
+			Password: &CryptPass,
+		},
+		blueprint.UserCustomization{
+			Name:     "maggie",
+			Password: &EmptyPass,
+		},
+		blueprint.UserCustomization{
+			Name: "homer",
+		},
+	}
+
+	options, err := NewUsersStageOptions(users, false)
+	require.Nil(t, err)
+	require.NotNil(t, options)
+
+	// bart's password should now be a hash
+	assert.True(t, strings.HasPrefix(*options.Users["bart"].Password, "$6$"))
+
+	// lisa's password should be left alone (already hashed)
+	assert.Equal(t, CryptPass, *options.Users["lisa"].Password)
+
+	// maggie's password should now be nil (locked account)
+	assert.Nil(t, options.Users["maggie"].Password)
+
+	// homer's password should still be nil (locked account)
+	assert.Nil(t, options.Users["homer"].Password)
+}
diff --git a/test/cases/api.sh b/test/cases/api.sh
index ed0e605b8..a1eaf1be3 100755
--- a/test/cases/api.sh
+++ b/test/cases/api.sh
@@ -80,8 +80,7 @@ function greenprint {
     echo -e "\033[1;32m[$(date -Isecond)] ${1}\033[0m"
 }
 
-ARTIFACTS=ci-artifacts
-mkdir -p "${ARTIFACTS}"
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 
@@ -194,7 +193,7 @@ function dump_db() {
 
   # Save the result, including the manifest, for the job, straight from the db
   sudo ${CONTAINER_RUNTIME} exec "${DB_CONTAINER_NAME}" psql -U postgres -d osbuildcomposer -c "SELECT result FROM jobs WHERE type='manifest-id-only'" \
-    | gpg --batch --yes --passphrase "${GPG_SYMMETRIC_PASSPHRASE}" -o "${ARTIFACTS}/build-result.gpg" --symmetric -
+    | sudo tee "${ARTIFACTS}/build-result.txt"
   set -x
 }
 
diff --git a/test/cases/api/common/s3.sh b/test/cases/api/common/s3.sh
index bf194a087..a3f812a24 100644
--- a/test/cases/api/common/s3.sh
+++ b/test/cases/api/common/s3.sh
@@ -383,6 +383,13 @@ function verifyInVSphere() {
         -on=false \
         "${VSPHERE_VM_NAME}"
 
+    # tagging vm as testing object
+    $GOVC_CMD tags.attach \
+    	-u "${GOVMOMI_USERNAME}":"${GOVMOMI_PASSWORD}"@"${GOVMOMI_URL}" \
+    	-k=true \
+    	-c "osbuild-composer testing" gitlab-ci-test \
+    	"/${GOVMOMI_DATACENTER}/vm/${GOVMOMI_FOLDER}/${VSPHERE_VM_NAME}"    
+
     # upload ISO, create CDROM device and insert the ISO in it
     greenprint "💿 ⬆️ Uploading the cloud-init user-data ISO to VSphere"
     VSPHERE_CIDATA_ISO_PATH="${VSPHERE_VM_NAME}/cidata.iso"
diff --git a/test/cases/aws.sh b/test/cases/aws.sh
index 98173d04c..e2b0e7894 100755
--- a/test/cases/aws.sh
+++ b/test/cases/aws.sh
@@ -60,12 +60,7 @@ else
   TEST_ID=$(uuidgen);
 fi
 
-
-# Jenkins sets WORKSPACE to the job workspace, but if this script runs
-# outside of Jenkins, we can set up a temporary directory instead.
-if [[ ${WORKSPACE:-empty} == empty ]]; then
-    WORKSPACE=$(mktemp -d)
-fi
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Set up temporary files.
 AWS_CONFIG=${TEMPDIR}/aws.toml
@@ -93,7 +88,7 @@ $AWS_CMD --version
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
-    LOG_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-aws.log
+    LOG_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-aws.log
 
     # Download the logs.
     sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null
@@ -102,7 +97,7 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-aws.json
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-aws.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null
@@ -257,6 +252,9 @@ AWS_ACCESS_KEY_ID=${V2_AWS_ACCESS_KEY_ID} \
 AWS_SECRET_ACCESS_KEY=${V2_AWS_SECRET_ACCESS_KEY} \
 python3 cloud-image-val.py -r resource-file.json -d -o report.xml -m 'not pub' && RESULTS=1 || RESULTS=0
 
+# copy the report to artifacts folder
+cp report.html "${ARTIFACTS}"
+
 popd
 
 # Clean up our mess.
diff --git a/test/cases/azure.sh b/test/cases/azure.sh
index af3f3e114..53643b4d9 100755
--- a/test/cases/azure.sh
+++ b/test/cases/azure.sh
@@ -94,11 +94,7 @@ ARCH=$(uname -m)
 TEST_ID="$DISTRO_CODE-$ARCH-$BRANCH_NAME-$BUILD_ID"
 IMAGE_KEY=image-${TEST_ID}
 
-# Jenkins sets WORKSPACE to the job workspace, but if this script runs
-# outside of Jenkins, we can set up a temporary directory instead.
-if [[ ${WORKSPACE:-empty} == empty ]]; then
-    WORKSPACE=$(mktemp -d)
-fi
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Set up temporary files.
 AZURE_CONFIG=${TEMPDIR}/azure.toml
@@ -119,7 +115,7 @@ smoke_test_check () {
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
-    LOG_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-azure.log
+    LOG_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-azure.log
 
     # Download the logs.
     sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null
@@ -128,7 +124,7 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-azure.json
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-azure.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null
diff --git a/test/cases/container-upload.sh b/test/cases/container-upload.sh
index 3bce7ba15..14fd95239 100755
--- a/test/cases/container-upload.sh
+++ b/test/cases/container-upload.sh
@@ -23,6 +23,7 @@ source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 
 /usr/libexec/osbuild-composer-test/provision.sh
 
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 TEMPDIR=$(mktemp -d)
 function cleanup() {
     greenprint "== Script execution stopped or finished - Cleaning up =="
@@ -33,7 +34,7 @@ trap cleanup EXIT
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
-    LOG_FILE=${TEMPDIR}/osbuild-${ID}-${VERSION_ID}-azure.log
+    LOG_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-azure.log
 
     # Download the logs.
     sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null
@@ -42,7 +43,7 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${TEMPDIR}/osbuild-${ID}-${VERSION_ID}-azure.json
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-azure.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null
diff --git a/test/cases/gcp.sh b/test/cases/gcp.sh
index e036d3d2f..60735c311 100755
--- a/test/cases/gcp.sh
+++ b/test/cases/gcp.sh
@@ -85,12 +85,7 @@ else
     TEST_ID=$(uuidgen);
 fi
 
-
-# Jenkins sets WORKSPACE to the job workspace, but if this script runs
-# outside of Jenkins, we can set up a temporary directory instead.
-if [[ ${WORKSPACE:-empty} == empty ]]; then
-    WORKSPACE=$(mktemp -d)
-fi
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Set up temporary files.
 GCP_CONFIG=${TEMPDIR}/gcp.toml
@@ -199,7 +194,7 @@ function _instanceCheck() {
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
-    LOG_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-gcp.log
+    LOG_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-gcp.log
 
     # Download the logs.
     sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null
@@ -208,7 +203,7 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-gcp.json
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-gcp.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null
diff --git a/test/cases/installers.sh b/test/cases/installers.sh
index a913f2dc3..3006a86d2 100755
--- a/test/cases/installers.sh
+++ b/test/cases/installers.sh
@@ -138,9 +138,7 @@ SSH_USER="admin"
 IMAGE_KEY="osbuild-composer-installer-test-${TEST_UUID}"
 GUEST_ADDRESS=192.168.100.50
 
-if [[ ${WORKSPACE:-empty} == empty ]]; then
-    WORKSPACE=$(mktemp -d)
-fi
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Set up temporary files.
 TEMPDIR=$(mktemp -d)
@@ -154,8 +152,6 @@ SSH_DATA_DIR=$(/usr/libexec/osbuild-composer-test/gen-ssh.sh)
 SSH_KEY=${SSH_DATA_DIR}/id_rsa
 SSH_KEY_PUB=$(cat "${SSH_KEY}".pub)
 
-ARTIFACTS="ci-artifacts"
-mkdir -p "${ARTIFACTS}"
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
@@ -169,8 +165,8 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-${COMPOSE_ID}.json
     greenprint "Saving manifest for ${COMPOSE_ID}"
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-${COMPOSE_ID}.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null
diff --git a/test/cases/manifest_tests.sh b/test/cases/manifest_tests.sh
index da348af7b..62ad8c405 100755
--- a/test/cases/manifest_tests.sh
+++ b/test/cases/manifest_tests.sh
@@ -6,8 +6,7 @@ DNF_JSON_PATH="/usr/libexec/osbuild-composer/dnf-json"
 IMAGE_TEST_CASES_PATH="/usr/share/tests/osbuild-composer/manifests"
 
 WORKING_DIRECTORY=/usr/libexec/osbuild-composer
-mkdir --parents /tmp/logs
-LOGS_DIRECTORY=$(mktemp --directory --tmpdir=/tmp/logs)
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Print out a nice test divider so we know when tests stop and start.
 test_divider () {
@@ -27,7 +26,7 @@ test_divider
 echo "🏃🏻 Running test: ${TEST_NAME}"
 test_divider
 
-if sudo "$MANIFEST_TESTS_RUNNER" -test.v -manifests-path "$IMAGE_TEST_CASES_PATH" -dnf-json-path "$DNF_JSON_PATH" | tee "${LOGS_DIRECTORY}"/"${TEST_NAME}".log; then
+if sudo "$MANIFEST_TESTS_RUNNER" -test.v -manifests-path "$IMAGE_TEST_CASES_PATH" -dnf-json-path "$DNF_JSON_PATH" | tee "${ARTIFACTS}"/"${TEST_NAME}".log; then
     echo "🎉  Test passed."
     exit 0
 else
diff --git a/test/cases/ostree-ng.sh b/test/cases/ostree-ng.sh
index 32ae631d1..01bf7af24 100755
--- a/test/cases/ostree-ng.sh
+++ b/test/cases/ostree-ng.sh
@@ -84,13 +84,12 @@ QUAY_REPO_URL="quay.io/osbuild/testing-rhel-edge-push"
 QUAY_REPO_TAG=$(tr -dc a-z0-9 < /dev/urandom | head -c 4 ; echo '')
 STAGE_OCP4_SERVER_NAME="edge-stage-server"
 STAGE_OCP4_REPO_URL="http://${STAGE_OCP4_SERVER_NAME}-${QUAY_REPO_TAG}-frontdoor.apps.ocp.ci.centos.org/repo/"
-ARTIFACTS="ci-artifacts"
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 # For CS8, CS9, RHEL 8.5 and above
 CONTAINER_TYPE=edge-container
 CONTAINER_FILENAME=container.tar
 INSTALLER_TYPE=edge-installer
 INSTALLER_FILENAME=installer.iso
-mkdir -p "${ARTIFACTS}"
 ANSIBLE_USER_FOR_BIOS="installeruser"
 OSTREE_OSNAME=rhel
 
diff --git a/test/cases/ostree-raw-image.sh b/test/cases/ostree-raw-image.sh
index 542b7733c..ed787c0a0 100755
--- a/test/cases/ostree-raw-image.sh
+++ b/test/cases/ostree-raw-image.sh
@@ -76,12 +76,11 @@ PROD_REPO_URL=http://192.168.100.1/repo
 PROD_REPO=/var/www/html/repo
 STAGE_REPO_ADDRESS=192.168.200.1
 STAGE_REPO_URL="http://${STAGE_REPO_ADDRESS}:8080/repo/"
-ARTIFACTS="ci-artifacts"
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 CONTAINER_TYPE=edge-container
 CONTAINER_FILENAME=container.tar
 INSTALLER_TYPE=edge-raw-image
 INSTALLER_FILENAME=image.raw.xz
-mkdir -p "${ARTIFACTS}"
 
 # Set up temporary files.
 TEMPDIR=$(mktemp -d)
diff --git a/test/cases/ostree-rebase.sh b/test/cases/ostree-rebase.sh
index 8ce935b5e..147796f78 100755
--- a/test/cases/ostree-rebase.sh
+++ b/test/cases/ostree-rebase.sh
@@ -78,10 +78,9 @@ BIOS_GUEST_ADDRESS=192.168.100.50
 UEFI_GUEST_ADDRESS=192.168.100.51
 PROD_REPO_ADDRESS=192.168.200.1
 PROD_REPO_URL="http://${PROD_REPO_ADDRESS}:8080/repo/"
-ARTIFACTS="ci-artifacts"
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 CONTAINER_TYPE=edge-container
 CONTAINER_FILENAME=container.tar
-mkdir -p "${ARTIFACTS}"
 
 # Set up temporary files.
 TEMPDIR=$(mktemp -d)
diff --git a/test/cases/ostree-simplified-installer.sh b/test/cases/ostree-simplified-installer.sh
index d864e70c9..f7e790c8c 100755
--- a/test/cases/ostree-simplified-installer.sh
+++ b/test/cases/ostree-simplified-installer.sh
@@ -94,12 +94,11 @@ FDO_SERVER_ADDRESS=192.168.200.2
 FDO_ADMIN_CLI_VERSION=0.4.0
 # FDO Manualfacture server image version
 FDO_MF_SERVER_VERSION=0.4.0
-ARTIFACTS="ci-artifacts"
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 CONTAINER_TYPE=edge-container
 CONTAINER_FILENAME=container.tar
 INSTALLER_TYPE=edge-simplified-installer
 INSTALLER_FILENAME=simplified-installer.iso
-mkdir -p "${ARTIFACTS}"
 
 # Set up temporary files.
 TEMPDIR=$(mktemp -d)
diff --git a/test/cases/ostree.sh b/test/cases/ostree.sh
index 3d9ae3827..68f4c8ce8 100755
--- a/test/cases/ostree.sh
+++ b/test/cases/ostree.sh
@@ -151,8 +151,7 @@ TEST_UUID=$(uuidgen)
 IMAGE_KEY="osbuild-composer-ostree-test-${TEST_UUID}"
 GUEST_ADDRESS=192.168.100.50
 SSH_USER="admin"
-ARTIFACTS="ci-artifacts"
-mkdir -p "${ARTIFACTS}"
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Set up temporary files.
 TEMPDIR=$(mktemp -d)
diff --git a/test/cases/regression-old-worker-new-composer.sh b/test/cases/regression-old-worker-new-composer.sh
index 0e1920b18..d53f01f49 100644
--- a/test/cases/regression-old-worker-new-composer.sh
+++ b/test/cases/regression-old-worker-new-composer.sh
@@ -12,8 +12,7 @@ function greenprint {
     echo -e "\033[1;32m[$(date -Isecond)] ${1}\033[0m"
 }
 
-ARTIFACTS=ci-artifacts
-mkdir -p "${ARTIFACTS}"
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 
diff --git a/test/cases/upgrade8to9.sh b/test/cases/upgrade8to9.sh
index 8335a5e63..f8179c9ac 100755
--- a/test/cases/upgrade8to9.sh
+++ b/test/cases/upgrade8to9.sh
@@ -4,6 +4,8 @@ set -uxo pipefail
 # Get OS data.
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
+
 # Colorful output.
 function greenprint {
     echo -e "\033[1;32m[$(date -Isecond)] ${1}\033[0m"
@@ -123,9 +125,9 @@ sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" /usr/share/tests/osbuild-compose
 sudo ssh "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS" 'sed -i "s/PermitRootLogin yes/PermitRootLogin yes #for sure/" /etc/ssh/sshd_config'
 set +e
 sudo ssh "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS" 'source /root/upgrade_prepare.sh'
-sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":/var/log/leapp/leapp-preupgrade.log .
-sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":/var/log/leapp/leapp-upgrade.log .
-sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":/var/log/leapp/leapp-report.txt .
+sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":/var/log/leapp/leapp-preupgrade.log "$ARTIFACTS"
+sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":/var/log/leapp/leapp-upgrade.log "$ARTIFACTS"
+sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":/var/log/leapp/leapp-report.txt "$ARTIFACTS"
 set -e
 
 # watch and log the console during upgrade
@@ -143,7 +145,7 @@ RESULT="$?"
 set -e
 
 # copy over osbuild-composer logs
-sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":logs/* .
+sudo scp "${SSH_OPTIONS[@]}" -q -i "${SSH_KEY}" root@"$INSTANCE_ADDRESS":logs/* "$ARTIFACTS"
 
 if [[ "$RESULT" == 0 ]]; then
   greenprint "💚 Success"
diff --git a/test/cases/vmware.sh b/test/cases/vmware.sh
index fd7848019..3fda494c4 100755
--- a/test/cases/vmware.sh
+++ b/test/cases/vmware.sh
@@ -59,11 +59,7 @@ fi
 TEST_UUID=$(uuidgen)
 IMAGE_KEY=osbuild-composer-vmware-test-${TEST_UUID}
 
-# Jenkins sets WORKSPACE to the job workspace, but if this script runs
-# outside of Jenkins, we can set up a temporary directory instead.
-if [[ ${WORKSPACE:-empty} == empty ]]; then
-    WORKSPACE=$(mktemp -d)
-fi
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Set up temporary files
 TEMPDIR=$(mktemp -d)
@@ -89,7 +85,7 @@ running_test_check () {
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
-    LOG_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-vmware.log
+    LOG_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-vmware.log
 
     # Download the logs.
     sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null
@@ -98,7 +94,7 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-vmware.json
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-vmware.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null
diff --git a/tools/libvirt_test.sh b/tools/libvirt_test.sh
index 9b6fd749b..559d281c2 100755
--- a/tools/libvirt_test.sh
+++ b/tools/libvirt_test.sh
@@ -90,10 +90,7 @@ BLUEPRINT_FILE=${TEMPDIR}/blueprint.toml
 COMPOSE_START=${TEMPDIR}/compose-start-${IMAGE_KEY}.json
 COMPOSE_INFO=${TEMPDIR}/compose-info-${IMAGE_KEY}.json
 
-if [[ ${WORKSPACE:-empty} == empty ]]; then
-    WORKSPACE=$(mktemp -d)
-fi
-
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 SSH_DATA_DIR=$(/usr/libexec/osbuild-composer-test/gen-ssh.sh)
 SSH_KEY=${SSH_DATA_DIR}/id_rsa
 
@@ -112,7 +109,7 @@ smoke_test_check () {
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
-    LOG_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-${IMAGE_TYPE}.log
+    LOG_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-${IMAGE_TYPE}.log
 
     # Download the logs.
     sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null
@@ -121,7 +118,7 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-${IMAGE_TYPE}.json
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-${IMAGE_TYPE}.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null
diff --git a/tools/s3_test.sh b/tools/s3_test.sh
index 70820c45b..68e406e0c 100755
--- a/tools/s3_test.sh
+++ b/tools/s3_test.sh
@@ -29,11 +29,7 @@ function cleanup() {
 }
 trap cleanup EXIT
 
-# Jenkins sets WORKSPACE to the job workspace, but if this script runs
-# outside of Jenkins, we can set up a temporary directory instead.
-if [[ ${WORKSPACE:-empty} == empty ]]; then
-    WORKSPACE=$(mktemp -d)
-fi
+ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 
 # Set up temporary files.
 BLUEPRINT_FILE=${TEMPDIR}/blueprint.toml
@@ -44,7 +40,7 @@ COMPOSE_INFO=${TEMPDIR}/compose-info-${TEST_ID}.json
 # Get the compose log.
 get_compose_log () {
     COMPOSE_ID=$1
-    LOG_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-aws.log
+    LOG_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-aws.log
 
     # Download the logs.
     sudo composer-cli compose log "$COMPOSE_ID" | tee "$LOG_FILE" > /dev/null
@@ -53,7 +49,7 @@ get_compose_log () {
 # Get the compose metadata.
 get_compose_metadata () {
     COMPOSE_ID=$1
-    METADATA_FILE=${WORKSPACE}/osbuild-${ID}-${VERSION_ID}-aws.json
+    METADATA_FILE=${ARTIFACTS}/osbuild-${ID}-${VERSION_ID}-aws.json
 
     # Download the metadata.
     sudo composer-cli compose metadata "$COMPOSE_ID" > /dev/null