Repository: osbuild/osbuild-composer
Pull request #2877 (2022-08-02 14:24:43)

Cloud API: add support for container embedding
Add support for embedding container images via the cloud API. For this the container resolve job was plumbed into the cloud api's handler and the API specification updated with a new `containers`section that mimics the blueprint section with the same name.


Patch:
diff --git a/cmd/osbuild-worker/main.go b/cmd/osbuild-worker/main.go
index 552383147..d5e6c94c5 100644
--- a/cmd/osbuild-worker/main.go
+++ b/cmd/osbuild-worker/main.go
@@ -128,6 +128,10 @@ func setProtection(protected bool) {
 		}
 		return
 	}
+	if len(asInstanceOutput.AutoScalingInstances) == 0 {
+		logrus.Info("No Autoscaling instace is defined")
+		return
+	}
 
 	// make the request to protect (or unprotect) the instance
 	input := &autoscaling.SetInstanceProtectionInput{
diff --git a/distribution/Dockerfile-config b/distribution/Dockerfile-config
index 20d2a9834..8ea6c5864 100644
--- a/distribution/Dockerfile-config
+++ b/distribution/Dockerfile-config
@@ -5,7 +5,7 @@ RUN dnf -y install \
 
 COPY ./tools/gen-certs.sh .
 COPY ./test/data/x509/openssl.cnf .
-COPY ./test/data/composer/osbuild-composer.toml /config/
-COPY ./test/data/worker/osbuild-worker.toml /config/
+COPY ./test/data/composer/osbuild-composer-tls.toml /config/
+COPY ./test/data/worker/osbuild-worker-tls.toml /config/
 COPY ./repositories /config/repositories/
 RUN ./gen-certs.sh ./openssl.cnf /config  /config/ca
diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go
index ba878406b..9a9ecbcac 100644
--- a/internal/cloudapi/v2/handler.go
+++ b/internal/cloudapi/v2/handler.go
@@ -173,6 +173,19 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 		}
 	}
 
+	if request.Customizations != nil && request.Customizations.Containers != nil {
+		for _, c := range *request.Customizations.Containers {
+			bc := blueprint.Container{
+				Source:    c.Source,
+				TLSVerify: c.TlsVerify,
+			}
+			if c.Name != nil {
+				bc.Name = *c.Name
+			}
+			bp.Containers = append(bp.Containers, bc)
+		}
+	}
+
 	if request.Customizations != nil && request.Customizations.Filesystem != nil {
 		var fsCustomizations []blueprint.FilesystemCustomization
 		for _, f := range *request.Customizations.Filesystem {
diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go
index bd43ca82a..91cd5ae99 100644
--- a/internal/cloudapi/v2/openapi.v2.gen.go
+++ b/internal/cloudapi/v2/openapi.v2.gen.go
@@ -220,6 +220,16 @@ type ComposeStatusError struct {
 // ComposeStatusValue defines model for ComposeStatusValue.
 type ComposeStatusValue string
 
+// Container defines model for Container.
+type Container struct {
+	// Name to use for the container from the image
+	Name   *string `json:"name,omitempty"`
+	Source string  `json:"source"`
+
+	// Control TLS verifification
+	TlsVerify *bool `json:"tls_verify,omitempty"`
+}
+
 // ContainerUploadOptions defines model for ContainerUploadOptions.
 type ContainerUploadOptions struct {
 	// Name for the created container image
@@ -240,6 +250,7 @@ type ContainerUploadStatus struct {
 
 // Customizations defines model for Customizations.
 type Customizations struct {
+	Containers *[]Container  `json:"containers,omitempty"`
 	Filesystem *[]Filesystem `json:"filesystem,omitempty"`
 	Packages   *[]string     `json:"packages,omitempty"`
 
@@ -692,112 +703,114 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"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",
+	"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",
+	"R6B2l8FfAAA=",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml
index 3d49c14cd..7229bd784 100644
--- a/internal/cloudapi/v2/openapi.v2.yml
+++ b/internal/cloudapi/v2/openapi.v2.yml
@@ -859,6 +859,11 @@ components:
     Customizations:
       type: object
       properties:
+        containers:
+          type: array
+          items:
+             $ref: '#/components/schemas/Container'
+             description: Container images to embed into the final artfact
         subscription:
           $ref: '#/components/schemas/Subscription'
         packages:
@@ -902,6 +907,22 @@ components:
               items:
                 type: string
                 example: "firewalld"
+    Container:
+      type: object
+      required:
+        - source
+      properties:
+        source:
+          type: string
+          desription: Reference to the container to embed
+          example: 'registry.example.com/image:tag'
+        name:
+          type: string
+          description: Name to use for the container from the image
+        tls_verify:
+          type: boolean
+          description: Control TLS verifification
+          example: true
     Filesystem:
       type: object
       required:
diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go
index a1acb9bb4..5a01977b7 100644
--- a/internal/cloudapi/v2/server.go
+++ b/internal/cloudapi/v2/server.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
 	"github.com/osbuild/osbuild-composer/internal/common"
+	"github.com/osbuild/osbuild-composer/internal/container"
 	"github.com/osbuild/osbuild-composer/internal/distro"
 	"github.com/osbuild/osbuild-composer/internal/distroregistry"
 	"github.com/osbuild/osbuild-composer/internal/prometheus"
@@ -112,7 +113,33 @@ func (s *Server) enqueueCompose(distribution distro.Distro, bp blueprint.Bluepri
 		return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
 	}
 
-	manifestJobID, err := s.workers.EnqueueManifestJobByID(&worker.ManifestJobByID{}, depsolveJobID, channel)
+	dependencies := []uuid.UUID{depsolveJobID}
+	var containerResolveJob uuid.UUID
+	if len(bp.Containers) > 0 {
+		job := worker.ContainerResolveJob{
+			Arch:  ir.arch.Name(),
+			Specs: make([]worker.ContainerSpec, len(bp.Containers)),
+		}
+
+		for i, c := range bp.Containers {
+			job.Specs[i] = worker.ContainerSpec{
+				Source:    c.Source,
+				Name:      c.Name,
+				TLSVerify: c.TLSVerify,
+			}
+		}
+
+		jobId, err := s.workers.EnqueueContainerResolveJob(&job, channel)
+
+		if err != nil {
+			return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
+		}
+
+		containerResolveJob = jobId
+		dependencies = append(dependencies, jobId)
+	}
+
+	manifestJobID, err := s.workers.EnqueueManifestJobByID(&worker.ManifestJobByID{}, dependencies, channel)
 	if err != nil {
 		return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
 	}
@@ -130,7 +157,7 @@ func (s *Server) enqueueCompose(distribution distro.Distro, bp blueprint.Bluepri
 
 	s.goroutinesGroup.Add(1)
 	go func() {
-		generateManifest(s.goroutinesCtx, s.workers, depsolveJobID, manifestJobID, ir.imageType, ir.repositories, ir.imageOptions, manifestSeed, bp.Customizations)
+		generateManifest(s.goroutinesCtx, s.workers, depsolveJobID, containerResolveJob, manifestJobID, ir.imageType, ir.repositories, ir.imageOptions, manifestSeed, bp.Customizations)
 		defer s.goroutinesGroup.Done()
 	}()
 
@@ -164,7 +191,33 @@ func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, releas
 			return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
 		}
 
-		manifestJobID, err := s.workers.EnqueueManifestJobByID(&worker.ManifestJobByID{}, depsolveJobID, channel)
+		var containerResolveJob uuid.UUID
+		dependencies := []uuid.UUID{depsolveJobID}
+		if len(bp.Containers) > 0 {
+			job := worker.ContainerResolveJob{
+				Arch:  ir.arch.Name(),
+				Specs: make([]worker.ContainerSpec, len(bp.Containers)),
+			}
+
+			for i, c := range bp.Containers {
+				job.Specs[i] = worker.ContainerSpec{
+					Source:    c.Source,
+					Name:      c.Name,
+					TLSVerify: c.TLSVerify,
+				}
+			}
+
+			jobId, err := s.workers.EnqueueContainerResolveJob(&job, channel)
+
+			if err != nil {
+				return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
+			}
+
+			containerResolveJob = jobId
+			dependencies = append(dependencies, jobId)
+		}
+
+		manifestJobID, err := s.workers.EnqueueManifestJobByID(&worker.ManifestJobByID{}, dependencies, channel)
 		if err != nil {
 			return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
 		}
@@ -208,7 +261,7 @@ func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, releas
 		// copy the image request while passing it into the goroutine to prevent data races
 		s.goroutinesGroup.Add(1)
 		go func(ir imageRequest) {
-			generateManifest(s.goroutinesCtx, s.workers, depsolveJobID, manifestJobID, ir.imageType, ir.repositories, ir.imageOptions, manifestSeed, bp.Customizations)
+			generateManifest(s.goroutinesCtx, s.workers, depsolveJobID, containerResolveJob, manifestJobID, ir.imageType, ir.repositories, ir.imageOptions, manifestSeed, bp.Customizations)
 			defer s.goroutinesGroup.Done()
 		}(ir)
 	}
@@ -229,7 +282,7 @@ func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, releas
 	return id, nil
 }
 
-func generateManifest(ctx context.Context, workers *worker.Server, depsolveJobID uuid.UUID, manifestJobID uuid.UUID, imageType distro.ImageType, repos []rpmmd.RepoConfig, options distro.ImageOptions, seed int64, b *blueprint.Customizations) {
+func generateManifest(ctx context.Context, workers *worker.Server, depsolveJobID, containerResolveJobID, manifestJobID uuid.UUID, imageType distro.ImageType, repos []rpmmd.RepoConfig, options distro.ImageOptions, seed int64, b *blueprint.Customizations) {
 	ctx, cancel := context.WithTimeout(ctx, time.Minute*5)
 	defer cancel()
 
@@ -313,7 +366,36 @@ func generateManifest(ctx context.Context, workers *worker.Server, depsolveJobID
 		return
 	}
 
-	manifest, err := imageType.Manifest(b, options, repos, depsolveResults.PackageSpecs, nil, seed)
+	var containerSpecs []container.Spec
+	if len(dynArgs) == 2 {
+		// Container resolve job
+		var result worker.ContainerResolveJobResult
+
+		_, err := workers.ContainerResolveJobInfo(containerResolveJobID, &result)
+
+		if err != nil {
+			reason := "Error reading container resolve job status"
+			jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorReadingJobStatus, reason, nil)
+			return
+		}
+
+		if jobErr := result.JobError; jobErr != nil {
+			jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorContainerDependency, "Error in container resolve job dependency", nil)
+			return
+		}
+
+		containerSpecs = make([]container.Spec, len(result.Specs))
+
+		for i, s := range result.Specs {
+			containerSpecs[i].Source = s.Source
+			containerSpecs[i].Digest = s.Digest
+			containerSpecs[i].LocalName = s.Name
+			containerSpecs[i].TLSVerify = s.TLSVerify
+			containerSpecs[i].ImageID = s.ImageID
+		}
+	}
+
+	manifest, err := imageType.Manifest(b, options, repos, depsolveResults.PackageSpecs, containerSpecs, seed)
 	if err != nil {
 		reason := "Error generating manifest"
 		jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorManifestGeneration, reason, nil)
diff --git a/internal/worker/clienterrors/errors.go b/internal/worker/clienterrors/errors.go
index 9e6003b51..c7a56d100 100644
--- a/internal/worker/clienterrors/errors.go
+++ b/internal/worker/clienterrors/errors.go
@@ -31,6 +31,7 @@ const (
 	ErrorTargetError          ClientErrorCode = 28
 	ErrorParsingJobArgs       ClientErrorCode = 29
 	ErrorContainerResolution  ClientErrorCode = 30
+	ErrorContainerDependency  ClientErrorCode = 31
 )
 
 type ClientErrorCode int
@@ -94,6 +95,8 @@ func GetStatusCode(err *Error) StatusCode {
 // IsDependencyError returns true if the error means that a dependency of a job failed
 func (e *Error) IsDependencyError() bool {
 	switch e.ID {
+	case ErrorContainerDependency:
+		return true
 	case ErrorDepsolveDependency:
 		return true
 	case ErrorManifestDependency:
diff --git a/internal/worker/server.go b/internal/worker/server.go
index 20c0e76e7..1289a0681 100644
--- a/internal/worker/server.go
+++ b/internal/worker/server.go
@@ -146,8 +146,11 @@ func (s *Server) EnqueueDepsolve(job *DepsolveJob, channel string) (uuid.UUID, e
 	return s.enqueue(JobTypeDepsolve, job, nil, channel)
 }
 
-func (s *Server) EnqueueManifestJobByID(job *ManifestJobByID, parent uuid.UUID, channel string) (uuid.UUID, error) {
-	return s.enqueue(JobTypeManifestIDOnly, job, []uuid.UUID{parent}, channel)
+func (s *Server) EnqueueManifestJobByID(job *ManifestJobByID, dependencies []uuid.UUID, channel string) (uuid.UUID, error) {
+	if len(dependencies) == 0 {
+		panic("EnqueueManifestJobByID has no dependencies, expected at least a depsolve job")
+	}
+	return s.enqueue(JobTypeManifestIDOnly, job, dependencies, channel)
 }
 
 func (s *Server) EnqueueContainerResolveJob(job *ContainerResolveJob, channel string) (uuid.UUID, error) {
@@ -622,6 +625,14 @@ func (s *Server) FinishJob(token uuid.UUID, result json.RawMessage) error {
 		}
 		jobResult = &kojiFinalizeJR.JobResult
 
+	case JobTypeContainerResolve:
+		var containerResolveJR ContainerResolveJobResult
+		jobInfo, err = s.ContainerResolveJobInfo(jobId, &containerResolveJR)
+		if err != nil {
+			return err
+		}
+		jobResult = &containerResolveJR.JobResult
+
 	default:
 		return fmt.Errorf("unexpected job type: %s", jobType)
 	}
diff --git a/osbuild-composer.spec b/osbuild-composer.spec
index 4b018c938..7e32c4194 100644
--- a/osbuild-composer.spec
+++ b/osbuild-composer.spec
@@ -205,6 +205,7 @@ install -m 0755 -vp tools/libvirt_test.sh                          %{buildroot}%
 install -m 0755 -vp tools/s3_test.sh                               %{buildroot}%{_libexecdir}/osbuild-composer-test/
 install -m 0755 -vp tools/generic_s3_test.sh                       %{buildroot}%{_libexecdir}/osbuild-composer-test/
 install -m 0755 -vp tools/generic_s3_https_test.sh                 %{buildroot}%{_libexecdir}/osbuild-composer-test/
+install -m 0755 -vp tools/run-mock-auth-servers.sh                 %{buildroot}%{_libexecdir}/osbuild-composer-test/
 install -m 0755 -vp tools/set-env-variables.sh                     %{buildroot}%{_libexecdir}/osbuild-composer-test/
 install -m 0755 -vp tools/test-case-generators/generate-test-cases %{buildroot}%{_libexecdir}/osbuild-composer-test/
 install -m 0755 -vd                                                %{buildroot}%{_libexecdir}/tests/osbuild-composer
diff --git a/schutzbot/team_ssh_keys.txt b/schutzbot/team_ssh_keys.txt
index 0f885eda0..8de500804 100644
--- a/schutzbot/team_ssh_keys.txt
+++ b/schutzbot/team_ssh_keys.txt
@@ -15,3 +15,4 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPqEtsCdSozq0DT8sOazpizsBP65Ni6SMqrQA85Wnfs1
 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC61wMCjOSHwbVb4VfVyl5sn497qW4PsdQ7Ty7aD6wDNZ/QjjULkDV/yW5WjDlDQ7UqFH0Sr7vywjqDizUAqK7zM5FsUKsUXWHWwg/ehKg8j9xKcMv11AkFoUoujtfAujnKODkk58XSA9whPr7qcw3vPrmog680pnMSzf9LC7J6kXfs6lkoKfBh9VnlxusCrw2yg0qI1fHAZBLPx7mW6+me71QZsS6sVz8v8KXyrXsKTdnF50FjzHcK9HXDBtSJS5wA3fkcRYymJe0o6WMWNdgSRVpoSiWaHHmFgdMUJaYoCfhXzyl7LtNb3Q+Sveg+tJK7JaRXBLMUllOlJ6ll5Hod yih-redhat@redhat.com
 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYB3SyAYj+V/kmAt594RlpZlXRvVJ2r8+G1Jgnr6ft8Y6vpNkWZxpTVWEJicLczGYpzvq2AjkNStigU9Q1M2F21Te3SzT2kgNVXsMTqou4X//ZX20zej3gyI+25mc4LdBWxFaLsyrFqD76Fro2rAuCoylrfeIQBvFWbilrR+cAV9tFrJT9I4RWYVL8v7EUtBeXarVFIjwcCALzLHxFl7S/pZuuWMyhyXup1UPR3Oirpuv3kWOsElVzGOxMWREE0eoCnGYKN2VCBx+igwQbi+x/cVSf49sFBVfdpPHUGse3KwS7ukfvpmmYm06dy2JS93JrRaCUUUw2DN8VjW7dIODv jrusz@redhat.com
 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINPExjjH74MOM6wrXEpRUg6I0dtRdAV3bAUY+u7WMc2G sanne@redhat.com
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDjNynFZPCEPVDyOB2yzrww5kxwK6MAb1D0GN5yP8y/iw+gtx+Hj3CqojHMTa/9r3q3R1TMgCITdvzAiKylbx/owV8bgXS1p8je2KirWx3o/Dy80AYsas2F+sodm5/FOz6LvcUZw2vZiVs1wp8dz7ak+pm6Xg7xa7511xO4T/HStzNUE/XSPYmC9LNJ+uVWTiCjTWlZxp1JcDVfO7k69F60u8D42e1Ty60IeNeJItX/o8FUjB/rMAAJRpjFpd/uyfPTWamjNoVzrB7chFxaemg2Nf8na6PHLAx8Gcxz2fdnnsg+M5vr6z0yVYz1cc8VOhYynQm9iISvTt6bDVEbWc2T thozza@redhat.com
diff --git a/tools/koji-compose.py b/tools/koji-compose.py
index 8f95d9f6c..c61f0c27d 100755
--- a/tools/koji-compose.py
+++ b/tools/koji-compose.py
@@ -1,4 +1,7 @@
 #!/usr/bin/python3
+# pylint: disable=invalid-name
+
+import argparse
 import json
 import sys
 import time
@@ -63,7 +66,8 @@ def upload_options_by_cloud_target(cloud_target):
             "region": os.getenv("AWS_REGION"),
             "share_with_accounts": [os.getenv("AWS_API_TEST_SHARE_ACCOUNT")]
         }
-    elif cloud_target == "azure":
+
+    if cloud_target == "azure":
         return {
             # image name is currently not set for Koji composes
             # "image_name": "",
@@ -72,7 +76,8 @@ def upload_options_by_cloud_target(cloud_target):
             "subscription_id": os.getenv("AZURE_SUBSCRIPTION_ID"),
             "tenant_id": os.getenv("AZURE_TENANT_ID")
         }
-    elif cloud_target == "gcp":
+
+    if cloud_target == "gcp":
         return {
             # image name is currently not set for Koji composes
             # "image_name": "",
@@ -80,8 +85,8 @@ def upload_options_by_cloud_target(cloud_target):
             "region": os.getenv("GCP_REGION"),
             "share_with_accounts": [os.getenv("GCP_API_TEST_SHARE_ACCOUNT")]
         }
-    else:
-        raise RuntimeError(f"unsupported target cloud: {cloud_target}")
+
+    raise RuntimeError(f"unsupported target cloud: {cloud_target}")
 
 
 def compose_request_cloud_upload(distro, koji, arch, cloud_target, image_type):
@@ -113,24 +118,73 @@ def compose_request_cloud_upload(distro, koji, arch, cloud_target, image_type):
     return req
 
 
+# Client for the Composer API.
+class ComposerAPIClient:
+
+    def __init__(self, api_url, refresh_token, auth_server):
+        self.api_url = api_url
+        self.refresh_token = refresh_token
+        self.auth_server = auth_server
+
+    def access_token(self):
+        resp = requests.post(self.auth_server + "/token", data={"refresh_token": self.refresh_token})
+        if resp.status_code != 200:
+            raise RuntimeError(f"failed to refresh token: {resp.text}")
+        return resp.json()["access_token"]
+
+    def submit_compose(self, request):
+        return requests.post(self.api_url + "/compose", json=request,
+                             headers={"Authorization": f"Bearer {self.access_token()}"})
+
+    def compose_status(self, compose_id):
+        return requests.get(self.api_url + f"/composes/{compose_id}",
+                            headers={"Authorization": f"Bearer {self.access_token()}"})
+
+    def compose_log(self, compose_id):
+        return requests.get(self.api_url + f"/composes/{compose_id}/logs",
+                            headers={"Authorization": f"Bearer {self.access_token()}"})
+
+
+def get_parser():
+    parser = argparse.ArgumentParser(description="Koji compose test")
+    parser.add_argument("distro", metavar="DISTRO", help="Distribution to build")
+    parser.add_argument("arch", metavar="ARCH", help="Architecture to build")
+    parser.add_argument("--refresh-token", metavar="TOKEN", help="JWT refresh token. " +
+                        "If not provided, read from /etc/osbuild-worker/token")
+    parser.add_argument("--auth-server", default="http://localhost:8081", help="Auth server URL")
+    parser.add_argument("--koji-url", default="https://localhost:4343/kojihub", help="Koji server to use")
+    parser.add_argument("--composer-url", default="http://localhost:443/api/image-builder-composer/v2",
+                        help="Composer API server to use")
+
+    cloud_upload_group = parser.add_argument_group("Cloud upload options")
+    cloud_upload_group.add_argument("cloud_target", metavar="CLOUD-TARGET", nargs="?", help="Cloud target to use")
+    cloud_upload_group.add_argument("image_type", metavar="IMAGE-TYPE", nargs="?", help="Image type to build")
+
+    return parser
+
+
 def main():
-    koji_url = "https://localhost:4343/kojihub"
-    if len(sys.argv) == 3:
-        distro, arch = sys.argv[1], sys.argv[2]
-        print("Using simple Koji compose with 2 image requests", file=sys.stderr)
-        cr = compose_request(distro, koji_url, arch)
-    elif len(sys.argv) == 5:
-        distro, arch, cloud_target, image_type = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
-        print("Using Koji compose with 1 image requests and upload to cloud", file=sys.stderr)
-        cr = compose_request_cloud_upload(distro, koji_url, arch, cloud_target, image_type)
+    parser = get_parser()
+    args = parser.parse_args()
+
+    if args.cloud_target and not args.image_type or not args.cloud_target and args.image_type:
+        parser.error("CLOUD-TARGET and IMAGE-TYPE must be used together")
+
+    refresh_token = args.refresh_token
+    if not refresh_token:
+        with open("/etc/osbuild-worker/token", encoding="utf-8") as f:
+            refresh_token = f.read().strip()
+
+    composer_api_client = ComposerAPIClient(args.composer_url, refresh_token, args.auth_server)
+
+    if args.cloud_target is not None:
+        cr = compose_request_cloud_upload(args.distro, args.koji_url, args.arch, args.cloud_target, args.image_type)
     else:
-        print(f"usage: {sys.argv[0]} DISTRO ARCH [CLOUD_TARGET IMAGE_TYPE]", file=sys.stderr)
-        sys.exit(1)
+        cr = compose_request(args.distro, args.koji_url, args.arch)
+
     print(json.dumps(cr), file=sys.stderr)
 
-    r = requests.post("https://localhost/api/image-builder-composer/v2/compose", json=cr,
-                      cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"),
-                      verify="/etc/osbuild-composer/ca-crt.pem")
+    r = composer_api_client.submit_compose(cr)
     if r.status_code != 201:
         print("Failed to create compose", file=sys.stderr)
         print(r.text, file=sys.stderr)
@@ -141,36 +195,35 @@ def main():
     print(compose_id)
 
     while True:
-        r = requests.get(f"https://localhost/api/image-builder-composer/v2/composes/{compose_id}",
-                         cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"),
-                         verify="/etc/osbuild-composer/ca-crt.pem")
+        r = composer_api_client.compose_status(compose_id)
         if r.status_code != 200:
             print("Failed to get compose status", file=sys.stderr)
             print(r.text, file=sys.stderr)
             sys.exit(1)
         status = r.json()["status"]
         print(status, file=sys.stderr)
+
         if status == "success":
             print("Compose worked!", file=sys.stderr)
             print(r.text, file=sys.stderr)
             break
-        elif status == "failure":
+
+        if status == "failure":
             print("compose failed!", file=sys.stderr)
             print(r.text, file=sys.stderr)
             sys.exit(1)
-        elif status != "pending" and status != "running":
+
+        if status not in ("pending", "running"):
             print(f"unexpected status: {status}", file=sys.stderr)
             print(r.text, file=sys.stderr)
             sys.exit(1)
 
         time.sleep(10)
 
-    r = requests.get(f"https://localhost/api/image-builder-composer/v2/composes/{compose_id}/logs",
-                     cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"),
-                     verify="/etc/osbuild-composer/ca-crt.pem")
+    r = composer_api_client.compose_log(compose_id)
     logs = r.json()
     assert "image_builds" in logs
-    assert type(logs["image_builds"]) == list
+    assert isinstance(logs["image_builds"], list)
     assert len(logs["image_builds"]) == len(cr["image_requests"])
 
 
diff --git a/tools/provision.sh b/tools/provision.sh
index 680d860e9..4a94f5e97 100755
--- a/tools/provision.sh
+++ b/tools/provision.sh
@@ -7,6 +7,13 @@ source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 ARTIFACTS="${ARTIFACTS:=/tmp/artifacts}"
 mkdir -p "${ARTIFACTS}"
 
+# determine the authentication method used by composer
+AUTH_METHOD_TLS="tls"
+AUTH_METHOD_JWT="jwt"
+AUTH_METHOD_NONE="none"
+# default to TLS for now
+AUTH_METHOD="${1:-$AUTH_METHOD_TLS}"
+
 # 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.
@@ -34,122 +41,157 @@ if [[ "$VERSION_ID" == "9.0" || "$VERSION_ID" == "9" ]]; then
 fi
 
 sudo mkdir -p /etc/osbuild-composer
-sudo cp -a /usr/share/tests/osbuild-composer/composer/osbuild-composer.toml \
-    /etc/osbuild-composer/
-
 sudo mkdir -p /etc/osbuild-worker
-sudo cp -a /usr/share/tests/osbuild-composer/worker/osbuild-worker.toml \
-    /etc/osbuild-worker/
-
-# if GCP credentials are defined in the ENV, add them to the worker's configuration
-GOOGLE_APPLICATION_CREDENTIALS="${GOOGLE_APPLICATION_CREDENTIALS:-}"
-if [ -n "$GOOGLE_APPLICATION_CREDENTIALS" ]; then
-    # The credentials file must be copied to a different location. Jenkins places
-    # it into /tmp and as a result, the worker would not see it due to using PrivateTmp=true.
-    GCP_CREDS_WORKER_PATH="/etc/osbuild-worker/gcp-credentials.json"
-    sudo cp "$GOOGLE_APPLICATION_CREDENTIALS" "$GCP_CREDS_WORKER_PATH"
-    echo -e "\n[gcp]\ncredentials = \"$GCP_CREDS_WORKER_PATH\"\n" | sudo tee -a /etc/osbuild-worker/osbuild-worker.toml
-fi
 
-# if Azure credentials are defined in the env, create the credentials file
-V2_AZURE_CLIENT_ID="${V2_AZURE_CLIENT_ID:-}"
-V2_AZURE_CLIENT_SECRET="${V2_AZURE_CLIENT_SECRET:-}"
-if [[ -n "$V2_AZURE_CLIENT_ID" && -n "$V2_AZURE_CLIENT_SECRET" ]]; then
-    set +x
-    sudo tee /etc/osbuild-worker/azure-credentials.toml > /dev/null << EOF
+# osbuild-composer and worker need to be configured in a specific way only when using
+# some authentication method (Service scenario). In such case, also credentials for
+# interacting with cloud providers are configured directly in the worker. In addition,
+# no certificates need to be generated, because they are not used anywhere in this
+# scenario.
+if [[ "$AUTH_METHOD" != "$AUTH_METHOD_NONE" ]]; then
+    # Generate all X.509 certificates for the tests
+    # The whole generation is done in a $CADIR to better represent how osbuild-ca
+    # it.
+    CERTDIR=/etc/osbuild-composer
+    OPENSSL_CONFIG=/usr/share/tests/osbuild-composer/x509/openssl.cnf
+    CADIR=/etc/osbuild-composer-test/ca
+
+    scriptloc=$(dirname "$0")
+    sudo "${scriptloc}/gen-certs.sh" "${OPENSSL_CONFIG}" "${CERTDIR}" "${CADIR}"
+    sudo chown _osbuild-composer "${CERTDIR}"/composer-*.pem
+
+    # Copy the appropriate configuration files
+    if [[ "$AUTH_METHOD" == "$AUTH_METHOD_JWT" ]]; then
+        COMPOSER_TEST_CONFIG="/usr/share/tests/osbuild-composer/composer/osbuild-composer-jwt.toml"
+        WORKER_TEST_CONFIG="/usr/share/tests/osbuild-composer/worker/osbuild-worker-jwt.toml"
+
+        # Default orgID
+        sudo tee "/etc/osbuild-worker/token" >/dev/null <<EOF
+123456789
+EOF
+
+        /usr/libexec/osbuild-composer-test/run-mock-auth-servers.sh start
+
+    elif [[ "$AUTH_METHOD" == "$AUTH_METHOD_TLS" ]]; then
+        COMPOSER_TEST_CONFIG="/usr/share/tests/osbuild-composer/composer/osbuild-composer-tls.toml"
+        WORKER_TEST_CONFIG="/usr/share/tests/osbuild-composer/worker/osbuild-worker-tls.toml"
+    fi
+
+    sudo cp -a "$COMPOSER_TEST_CONFIG" /etc/osbuild-composer/osbuild-composer.toml
+    sudo cp -a "$WORKER_TEST_CONFIG" /etc/osbuild-worker/osbuild-worker.toml
+
+    # if GCP credentials are defined in the ENV, add them to the worker's configuration
+    GOOGLE_APPLICATION_CREDENTIALS="${GOOGLE_APPLICATION_CREDENTIALS:-}"
+    if [ -n "$GOOGLE_APPLICATION_CREDENTIALS" ]; then
+        # The credentials file must be copied to a different location. Jenkins places
+        # it into /tmp and as a result, the worker would not see it due to using PrivateTmp=true.
+        GCP_CREDS_WORKER_PATH="/etc/osbuild-worker/gcp-credentials.json"
+        sudo cp "$GOOGLE_APPLICATION_CREDENTIALS" "$GCP_CREDS_WORKER_PATH"
+        echo -e "\n[gcp]\ncredentials = \"$GCP_CREDS_WORKER_PATH\"\n" | sudo tee -a /etc/osbuild-worker/osbuild-worker.toml
+    fi
+
+    # if Azure credentials are defined in the env, create the credentials file
+    V2_AZURE_CLIENT_ID="${V2_AZURE_CLIENT_ID:-}"
+    V2_AZURE_CLIENT_SECRET="${V2_AZURE_CLIENT_SECRET:-}"
+    if [[ -n "$V2_AZURE_CLIENT_ID" && -n "$V2_AZURE_CLIENT_SECRET" ]]; then
+        set +x
+        sudo tee /etc/osbuild-worker/azure-credentials.toml > /dev/null << EOF
 client_id =     "$V2_AZURE_CLIENT_ID"
 client_secret = "$V2_AZURE_CLIENT_SECRET"
 EOF
-    sudo tee -a /etc/osbuild-worker/osbuild-worker.toml > /dev/null << EOF
+        sudo tee -a /etc/osbuild-worker/osbuild-worker.toml > /dev/null << EOF
 
 [azure]
 credentials = "/etc/osbuild-worker/azure-credentials.toml"
 EOF
-    set -x
-fi
+        set -x
+    fi
 
-# if AWS credentials are defined in the ENV, add them to the worker's configuration
-V2_AWS_ACCESS_KEY_ID="${V2_AWS_ACCESS_KEY_ID:-}"
-V2_AWS_SECRET_ACCESS_KEY="${V2_AWS_SECRET_ACCESS_KEY:-}"
-if [[ -n "$V2_AWS_ACCESS_KEY_ID" && -n "$V2_AWS_SECRET_ACCESS_KEY" ]]; then
-    set +x
+    # if AWS credentials are defined in the ENV, add them to the worker's configuration
+    V2_AWS_ACCESS_KEY_ID="${V2_AWS_ACCESS_KEY_ID:-}"
+    V2_AWS_SECRET_ACCESS_KEY="${V2_AWS_SECRET_ACCESS_KEY:-}"
+    if [[ -n "$V2_AWS_ACCESS_KEY_ID" && -n "$V2_AWS_SECRET_ACCESS_KEY" ]]; then
+        set +x
     sudo tee /etc/osbuild-worker/aws-credentials.toml > /dev/null << EOF
 [default]
 aws_access_key_id = "$V2_AWS_ACCESS_KEY_ID"
 aws_secret_access_key = "$V2_AWS_SECRET_ACCESS_KEY"
 EOF
-    sudo tee -a /etc/osbuild-worker/osbuild-worker.toml > /dev/null << EOF
+        sudo tee -a /etc/osbuild-worker/osbuild-worker.toml > /dev/null << EOF
 
 [aws]
 credentials = "/etc/osbuild-worker/aws-credentials.toml"
 bucket = "${AWS_BUCKET}"
 EOF
-    set -x
-fi
+        set -x
+    fi
 
-# Copy rpmrepo snapshots for use in weldr tests
-REPODIR=/etc/osbuild-composer/repositories
-sudo mkdir -p $REPODIR
-# Copy all fedora repo overrides
-sudo cp -a /usr/share/tests/osbuild-composer/repositories/{fedora,centos}-*.json "$REPODIR"
-# Copy RHEL point release repos
-sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-85.json "$REPODIR"
-sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-86.json "$REPODIR"
-sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-87.json "$REPODIR"
-sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-90.json "$REPODIR"
-sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-91.json "$REPODIR"
-
-# RHEL nightly repos need to be overridden
-case "${ID}-${VERSION_ID}" in
-    "rhel-8.6")
-        # Override old rhel-8.json and rhel-8-beta.json because RHEL 8.6 test needs nightly repos
-        sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-86.json "$REPODIR/rhel-8.json"
-        # If multiple tests are run and call provision.sh the symlink will need to be overridden with -f
-        sudo ln -sf /etc/osbuild-composer/repositories/rhel-8.json "$REPODIR/rhel-8-beta.json"
-        ;;
-    "rhel-9.0")
-        # Override old rhel-90.json and rhel-90-beta.json because RHEL 9.0 test needs nightly repos
-        sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-90.json "$REPODIR/rhel-90.json"
-        # If multiple tests are run and call provision.sh the symlink will need to be overridden with -f
-        sudo ln -sf /etc/osbuild-composer/repositories/rhel-90.json "$REPODIR/rhel-90-beta.json"
-        ;;
-    *) ;;
-esac
+else # AUTH_METHOD_NONE
+    # Repositories in /etc/osbuild-composer/repositories are used only in the
+    # on-premise scenario (Weldr).
+    # Copy rpmrepo snapshots for use in weldr tests
+    REPODIR=/etc/osbuild-composer/repositories
+    sudo mkdir -p $REPODIR
+    # Copy all fedora repo overrides
+    sudo cp -a /usr/share/tests/osbuild-composer/repositories/{fedora,centos}-*.json "$REPODIR"
+    # Copy RHEL point release repos
+    sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-85.json "$REPODIR"
+    sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-86.json "$REPODIR"
+    sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-87.json "$REPODIR"
+    sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-90.json "$REPODIR"
+    sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-91.json "$REPODIR"
+
+    # RHEL nightly repos need to be overridden
+    case "${ID}-${VERSION_ID}" in
+        "rhel-8.6")
+            # Override old rhel-8.json and rhel-8-beta.json because RHEL 8.6 test needs nightly repos
+            sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-86.json "$REPODIR/rhel-8.json"
+            # If multiple tests are run and call provision.sh the symlink will need to be overridden with -f
+            sudo ln -sf /etc/osbuild-composer/repositories/rhel-8.json "$REPODIR/rhel-8-beta.json"
+            ;;
+        "rhel-9.0")
+            # Override old rhel-90.json and rhel-90-beta.json because RHEL 9.0 test needs nightly repos
+            sudo cp /usr/share/tests/osbuild-composer/repositories/rhel-90.json "$REPODIR/rhel-90.json"
+            # If multiple tests are run and call provision.sh the symlink will need to be overridden with -f
+            sudo ln -sf /etc/osbuild-composer/repositories/rhel-90.json "$REPODIR/rhel-90-beta.json"
+            ;;
+        *) ;;
+    esac
+
+    # overrides for RHEL nightly builds testing
+    VERSION_SUFFIX=$(echo "${VERSION_ID}" | tr -d ".")
+    if [ -f "rhel-${VERSION_ID%.*}.json" ]; then
+        sudo cp rhel-"${VERSION_ID%.*}".json "$REPODIR/rhel-${VERSION_SUFFIX}.json"
+    fi
 
-# overrides for RHEL nightly builds testing
-VERSION_SUFFIX=$(echo "${VERSION_ID}" | tr -d ".")
-if [ -f "rhel-${VERSION_ID%.*}.json" ]; then
-    sudo cp rhel-"${VERSION_ID%.*}".json "$REPODIR/rhel-${VERSION_SUFFIX}.json"
+    if [ -f "rhel-${VERSION_ID%.*}-beta.json" ]; then
+        sudo cp rhel-"${VERSION_ID%.*}"-beta.json "$REPODIR/rhel-${VERSION_SUFFIX}-beta.json"
+    fi
 fi
 
-if [ -f "rhel-${VERSION_ID%.*}-beta.json" ]; then
-    sudo cp rhel-"${VERSION_ID%.*}"-beta.json "$REPODIR/rhel-${VERSION_SUFFIX}-beta.json"
-fi
+# start appropriate units
+case "${AUTH_METHOD}" in
+    "${AUTH_METHOD_JWT}" | "${AUTH_METHOD_TLS}")
+        # JWT / TLS are used only in the "Service" scenario. This means that:
+        # - only remote workers will be used (no local worker)
+        # - only Cloud API socket will be started (no Weldr API)
+        sudo systemctl stop 'osbuild*'
+        # make sure that the local worker is not running
+        sudo systemctl mask osbuild-worker@1.service
+        # enable remote worker API
+        sudo systemctl start osbuild-remote-worker.socket
+        # enable Cloud API
+        sudo systemctl start osbuild-composer-api.socket
+        # start a remote worker
+        sudo systemctl start osbuild-remote-worker@localhost:8700.service
+        ;;
 
-# Generate all X.509 certificates for the tests
-# The whole generation is done in a $CADIR to better represent how osbuild-ca
-# it.
-CERTDIR=/etc/osbuild-composer
-OPENSSL_CONFIG=/usr/share/tests/osbuild-composer/x509/openssl.cnf
-CADIR=/etc/osbuild-composer-test/ca
-
-scriptloc=$(dirname "$0")
-sudo "${scriptloc}/gen-certs.sh" "${OPENSSL_CONFIG}" "${CERTDIR}" "${CADIR}"
-sudo chown _osbuild-composer "${CERTDIR}"/composer-*.pem
-
-sudo systemctl start osbuild-remote-worker.socket
-sudo systemctl start osbuild-composer.socket
-sudo systemctl start osbuild-composer-api.socket
-
-# The keys were regenerated but osbuild-composer might be already running.
-# Let's try to restart it. In ideal world, this shouldn't be needed as every
-# test case is supposed to run on a pristine machine. However, this is
-# currently not true on Schutzbot
-sudo systemctl try-restart osbuild-composer
-
-# Basic verification
-sudo composer-cli status show
-sudo composer-cli sources list
-for SOURCE in $(sudo composer-cli sources list); do
-    sudo composer-cli sources info "$SOURCE"
-done
+    "${AUTH_METHOD_NONE}")
+        # No authentication method is used on-premise with Weldr. This means that:
+        # - only local worker will be used (started automatically)
+        # - only Weldr API socket will be started
+        sudo systemctl stop 'osbuild*'
+        # enable Weldr API
+        sudo systemctl start osbuild-composer.socket
+        ;;
+esac
diff --git a/tools/run-mock-auth-servers.sh b/tools/run-mock-auth-servers.sh
new file mode 100644
index 000000000..fee2dafb0
--- /dev/null
+++ b/tools/run-mock-auth-servers.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -eu
+
+# Colorful output.
+function greenprint {
+    echo -e "\033[1;32m[$(date -Isecond)] ${1}\033[0m"
+}
+
+servers_start() {
+    greenprint "Starting mock JWT AUTH servers"
+    # Spin up an https instance for the composer-api and worker-api; the auth handler needs to hit an ssl `/certs` endpoint
+    sudo /usr/libexec/osbuild-composer-test/osbuild-mock-openid-provider -a localhost:8082 -rsaPubPem /etc/osbuild-composer/client-crt.pem -rsaPem /etc/osbuild-composer/client-key.pem -cert /etc/osbuild-composer/composer-crt.pem -key /etc/osbuild-composer/composer-key.pem &
+    # Spin up an http instance for the worker client to bypass the need to specify an extra CA
+    sudo /usr/libexec/osbuild-composer-test/osbuild-mock-openid-provider -a localhost:8081 -rsaPubPem /etc/osbuild-composer/client-crt.pem -rsaPem /etc/osbuild-composer/client-key.pem &
+}
+
+servers_stop() {
+    greenprint "Stopping mock JWT AUTH servers"
+    local KILL_PIDS=()
+    # shellcheck disable=SC2207
+    # The split is desired and should be simple enough for the shell to handle
+    KILL_PIDS=($(pgrep -f '^sudo /usr/libexec/osbuild-composer-test/osbuild-mock-openid-provider'))
+    for PID in "${KILL_PIDS[@]}"; do
+        sudo pkill -P "$PID"
+    done
+}
+
+case "$1" in
+    "start")
+        servers_start
+        ;;
+    "stop")
+        servers_stop
+        ;;
+    *)
+        echo "Usage: $0 {start|stop}"
+        exit 1
+        ;;
+esac


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a0cbc1256..8a856b25c 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -88,7 +88,7 @@ jobs:
     steps:
 
       - name: Install build and test dependencies
-        run: dnf -y install python3-pylint git-core
+        run: dnf -y install python3-pylint git-core python3-requests
 
       - name: Check out code into the Go module directory
         uses: actions/checkout@v3
@@ -97,7 +97,7 @@ jobs:
 
       - name: Analysing the code with pylint
         run: |
-          python3 -m pylint dnf-json
+          python3 -m pylint dnf-json tools/koji-compose.py
 
   lint:
     name: "⌨ Lint"
diff --git a/internal/worker/server_test.go b/internal/worker/server_test.go
index 76fa1682a..b08416e37 100644
--- a/internal/worker/server_test.go
+++ b/internal/worker/server_test.go
@@ -372,7 +372,7 @@ func TestRequestJobById(t *testing.T) {
 	depsolveJobId, err := server.EnqueueDepsolve(&worker.DepsolveJob{}, "")
 	require.NoError(t, err)
 
-	jobId, err := server.EnqueueManifestJobByID(&worker.ManifestJobByID{}, depsolveJobId, "")
+	jobId, err := server.EnqueueManifestJobByID(&worker.ManifestJobByID{}, []uuid.UUID{depsolveJobId}, "")
 	require.NoError(t, err)
 
 	test.TestRoute(t, server.Handler(), false, "POST", "/api/worker/v1/jobs", fmt.Sprintf(`{"arch":"arch","types":["%s"]}`, worker.JobTypeManifestIDOnly), http.StatusBadRequest,
@@ -742,10 +742,10 @@ func enqueueAndFinishTestJobDependencies(s *worker.Server, deps []testJob) ([]uu
 
 		case *worker.ManifestJobByID:
 			job := dep.main.(*worker.ManifestJobByID)
-			if len(depUUIDs) != 1 {
-				return nil, fmt.Errorf("exactly one dependency is expected for ManifestJobByID, got: %d", len(depUUIDs))
+			if len(depUUIDs) < 1 {
+				return nil, fmt.Errorf("at least one dependency is expected for ManifestJobByID, got: %d", len(depUUIDs))
 			}
-			id, err = s.EnqueueManifestJobByID(job, depUUIDs[0], "")
+			id, err = s.EnqueueManifestJobByID(job, depUUIDs, "")
 			if err != nil {
 				return nil, err
 			}
@@ -780,6 +780,16 @@ func enqueueAndFinishTestJobDependencies(s *worker.Server, deps []testJob) ([]uu
 				return nil, err
 			}
 
+		case *worker.ContainerResolveJob:
+			job := dep.main.(*worker.ContainerResolveJob)
+			if len(depUUIDs) != 0 {
+				return nil, fmt.Errorf("dependencies are not supported for ContainerResolveJob, got: %d", len(depUUIDs))
+			}
+			id, err = s.EnqueueContainerResolveJob(job, "")
+			if err != nil {
+				return nil, err
+			}
+
 		default:
 			return nil, fmt.Errorf("unexpected job type")
 		}
@@ -942,6 +952,71 @@ func TestJobDependencyChainErrors(t *testing.T) {
 				Reason: "empty manifest received",
 			},
 		},
+		// osbuild + manifest + depsolve + container resolve
+		// failed container resolve
+		{
+			job: testJob{
+				main: &worker.OSBuildJob{},
+				deps: []testJob{
+					{
+						main:   &worker.KojiInitJob{},
+						result: &worker.KojiInitJobResult{},
+					},
+					{
+						main: &worker.ManifestJobByID{},
+						deps: []testJob{
+							{
+								main: &worker.ContainerResolveJob{},
+								result: &worker.ContainerResolveJobResult{
+									JobResult: worker.JobResult{
+										JobError: &clienterrors.Error{
+											ID:     clienterrors.ErrorContainerResolution,
+											Reason: "remote container not found",
+										},
+									},
+								},
+							},
+							{
+								main:   &worker.DepsolveJob{},
+								result: &worker.DepsolveJobResult{},
+							},
+						},
+						result: &worker.ManifestJobByIDResult{
+							JobResult: worker.JobResult{
+								JobError: &clienterrors.Error{
+									ID:     clienterrors.ErrorContainerDependency,
+									Reason: "container dependency job failed",
+								},
+							},
+						},
+					},
+				},
+				result: &worker.OSBuildJobResult{
+					JobResult: worker.JobResult{
+						JobError: &clienterrors.Error{
+							ID:     clienterrors.ErrorManifestDependency,
+							Reason: "manifest dependency job failed",
+						},
+					},
+				},
+			},
+			expectedError: &clienterrors.Error{
+				ID:     clienterrors.ErrorManifestDependency,
+				Reason: "manifest dependency job failed",
+				Details: []*clienterrors.Error{
+					{
+						ID:     clienterrors.ErrorContainerDependency,
+						Reason: "container dependency job failed",
+						Details: []*clienterrors.Error{
+							{
+								ID:     clienterrors.ErrorContainerResolution,
+								Reason: "remote container not found",
+							},
+						},
+					},
+				},
+			},
+		},
 		// koji-init + osbuild + manifest + depsolve
 		// failed depsolve
 		{
@@ -1325,7 +1400,7 @@ func TestJobDependencyChainErrors(t *testing.T) {
 				Reason: "koji-finalize failed",
 			},
 		},
-		// koji-init + (osbuild + manifest + depsolve) + (osbuild + manifest + depsolve) + koji-finalize
+		// koji-init + (osbuild + manifest + depsolve + container resolve) + (osbuild + manifest + depsolve) + koji-finalize
 		// all passed
 		{
 			job: testJob{
@@ -1346,6 +1421,10 @@ func TestJobDependencyChainErrors(t *testing.T) {
 							{
 								main: &worker.ManifestJobByID{},
 								deps: []testJob{
+									{
+										main:   &worker.ContainerResolveJob{},
+										result: &worker.ContainerResolveJobResult{},
+									},
 									{
 										main:   &worker.DepsolveJob{},
 										result: &worker.DepsolveJobResult{},
diff --git a/test/cases/api.sh b/test/cases/api.sh
index be96376b2..58414239f 100755
--- a/test/cases/api.sh
+++ b/test/cases/api.sh
@@ -431,9 +431,9 @@ waitForState "failure"
 # crashed/stopped/killed worker should result in a failed state
 sendCompose "$REQUEST_FILE"
 waitForState "building"
-sudo systemctl stop "osbuild-worker@*"
+sudo systemctl stop "osbuild-remote-worker@*"
 waitForState "failure"
-sudo systemctl start "osbuild-worker@1"
+sudo systemctl start "osbuild-remote-worker@localhost:8700.service"
 
 # full integration case
 INIT_COMPOSES="$(collectMetrics)"
@@ -565,7 +565,7 @@ TOKEN="$(curl --request POST \
         http://localhost:443/api/image-builder-composer/v2/composes/"$COMPOSE_ID")" = "401" ]
 
 
-sudo systemctl start osbuild-remote-worker@localhost:8700.service
+sudo systemctl restart osbuild-remote-worker@localhost:8700.service
 sudo systemctl is-active --quiet osbuild-remote-worker@localhost:8700.service
 
 exit 0
diff --git a/test/cases/api/common/common.sh b/test/cases/api/common/common.sh
index 56fe11296..ba5f1c44a 100644
--- a/test/cases/api/common/common.sh
+++ b/test/cases/api/common/common.sh
@@ -44,3 +44,48 @@ function _instanceCheck() {
     echo "Not RHEL OS. Skip subscription check."
   fi
 }
+
+WORKER_REFRESH_TOKEN_PATH="/etc/osbuild-worker/token"
+
+# Fetch a JWT token.
+# The token is fetched using the refresh token configured in the worker.
+function access_token {
+  local refresh_token
+  refresh_token="$(cat $WORKER_REFRESH_TOKEN_PATH)"
+  access_token_with_org_id "$refresh_token"
+}
+
+# Fetch a JWT token.
+# The token is fetched using the refresh token provided as an argument.
+function access_token_with_org_id {
+  local refresh_token="$1"
+  curl --request POST \
+    --data "refresh_token=$refresh_token" \
+    --header "Content-Type: application/x-www-form-urlencoded" \
+    --silent \
+    --show-error \
+    --fail \
+    localhost:8081/token | jq -r .access_token
+}
+
+# Get the compose status using a JWT token.
+# The token is fetched using the refresh token configured in the worker.
+function compose_status {
+  local compose="$1"
+  local refresh_token
+  refresh_token="$(cat $WORKER_REFRESH_TOKEN_PATH)"
+  compose_status_with_org_id "$compose" "$refresh_token"
+}
+
+# Get the compose status using a JWT token.
+# The token is fetched using the refresh token provided as the second argument.
+function compose_status_with_org_id {
+  local compose="$1"
+  local refresh_token="$2"
+  curl \
+    --silent \
+    --show-error \
+    --fail \
+    --header "Authorization: Bearer $(access_token_with_org_id "$refresh_token")" \
+    "http://localhost:443/api/image-builder-composer/v2/composes/$compose"
+}
diff --git a/test/cases/api/container.registry.sh b/test/cases/api/container.registry.sh
index 423ff2489..762fa40f6 100644
--- a/test/cases/api/container.registry.sh
+++ b/test/cases/api/container.registry.sh
@@ -11,6 +11,7 @@ function checkEnv() {
 
 # Global var for ostree ref
 export OSTREE_REF="test/osbuild/edge"
+export CONTAINER_SOURCE="registry.gitlab.com/redhat/services/products/image-builder/ci/osbuild-composer/fedora-minimal"
 
 function cleanup() {
     CONTAINER_NAME="${OSTREE_CONTAINER_NAME:-}"
@@ -34,7 +35,7 @@ domain="${CI_REGISTRY}"
 path_prefix="${CI_PROJECT_PATH}"
 EOF
 
-  sudo systemctl restart "osbuild-worker@1"
+  sudo systemctl restart "osbuild-remote-worker@localhost:8700"
 }
 
 function createReqFile() {
@@ -47,8 +48,14 @@ function createReqFile() {
         "baseurl": "$PAYLOAD_REPO_URL"
       }
     ],
+    "containers":[
+      {
+        "source": "$CONTAINER_SOURCE"
+      }
+    ],
     "packages": [
       "postgresql",
+      "podman",
       "dummy"
     ],
     "users":[
@@ -89,6 +96,7 @@ function verify() {
   OSTREE_CONTAINER_NAME=osbuild-test
   local IMAGE_URL
   IMAGE_URL=$(echo "$UPLOAD_OPTIONS" | jq -r '.url')
+
   sudo "${CONTAINER_RUNTIME}" run -d --name osbuild-test -p 8080:8080 "${IMAGE_URL}"
 
   GET_METADATA_CURL_REQUEST="curl --silent \
@@ -102,4 +110,21 @@ function verify() {
   SERVICED_OSTREE_COMMIT=$(curl http://localhost:8080/repo/refs/heads/${OSTREE_REF})
 
   test "${BUILD_OSTREE_COMMIT}" = "${SERVICED_OSTREE_COMMIT}"
+
+  # check the commit contains the container, for this we pull the commit into
+  # a repo and poke into the container storage to see if the container we put
+  # in there matches the one we expect to be in there.
+
+  local CONFIG_DIGEST
+  CONFIG_DIGEST=$(skopeo inspect --raw "docker://$CONTAINER_SOURCE" | jq -r .config.digest)
+
+  ostree init --repo=repo
+  ostree remote --repo=repo add --no-gpg-verify container http://localhost:8080/repo
+  sudo ostree pull --repo=repo container "${OSTREE_REF}"
+
+  local EMBEDDED_ID
+  EMBEDDED_ID=$(sudo ostree cat --repo=repo "${BUILD_OSTREE_COMMIT}" /usr/share/containers/storage/overlay-images/images.json | jq -r .[0].id)
+
+  echo -e "have: sha256:${EMBEDDED_ID}\nwant: ${CONFIG_DIGEST}"
+  test "sha256:${EMBEDDED_ID}" = "${CONFIG_DIGEST}"
 }
diff --git a/test/cases/api/generic.s3.sh b/test/cases/api/generic.s3.sh
index a7f6ec936..0301e0b04 100644
--- a/test/cases/api/generic.s3.sh
+++ b/test/cases/api/generic.s3.sh
@@ -89,7 +89,7 @@ region = "${MINIO_REGION}"
 bucket = "${MINIO_BUCKET}"
 EOF
 
-  sudo systemctl restart "osbuild-worker@1"
+  sudo systemctl restart "osbuild-remote-worker@localhost:8700"
 }
 
 # Unset AWS_REGION, region == "" in the request the worker will look for the generic s3
diff --git a/test/cases/aws.sh b/test/cases/aws.sh
index 30fe12db7..513b8c8b9 100755
--- a/test/cases/aws.sh
+++ b/test/cases/aws.sh
@@ -29,7 +29,7 @@ function get_build_info() {
 CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Check available container runtime
 if which podman 2>/dev/null >&2; then
diff --git a/test/cases/aws_s3.sh b/test/cases/aws_s3.sh
index 8e30bbe2a..c3fcdf044 100755
--- a/test/cases/aws_s3.sh
+++ b/test/cases/aws_s3.sh
@@ -19,7 +19,7 @@ set -euo pipefail
 CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Check available container runtime
 if which podman 2>/dev/null >&2; then
diff --git a/test/cases/azure.sh b/test/cases/azure.sh
index 53643b4d9..1a8c95f79 100755
--- a/test/cases/azure.sh
+++ b/test/cases/azure.sh
@@ -33,7 +33,7 @@ function get_build_info() {
 CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Check available container runtime
 if which podman 2>/dev/null >&2; then
diff --git a/test/cases/base_tests.sh b/test/cases/base_tests.sh
index ce7762d8a..fd986b749 100755
--- a/test/cases/base_tests.sh
+++ b/test/cases/base_tests.sh
@@ -13,10 +13,13 @@ LOGS_DIRECTORY=$(mktemp --directory --tmpdir=/tmp/logs)
 PASSED_TESTS=()
 FAILED_TESTS=()
 
-TEST_CASES=(
+TEST_CASES_ON_PREM=(
   "osbuild-weldr-tests"
   "osbuild-dnf-json-tests"
   "osbuild-composer-cli-tests"
+)
+
+TEST_CASES_SERVICE=(
   "osbuild-auth-tests"
 )
 
@@ -45,13 +48,20 @@ run_test_case () {
 
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Change to the working directory.
 cd $WORKING_DIRECTORY
 
 # Run each test case.
-for TEST_CASE in "${TEST_CASES[@]}"; do
+for TEST_CASE in "${TEST_CASES_ON_PREM[@]}"; do
+    run_test_case ${TESTS_PATH}/"$TEST_CASE"
+done
+
+/usr/libexec/osbuild-composer-test/provision.sh tls
+
+# Run each test case.
+for TEST_CASE in "${TEST_CASES_SERVICE[@]}"; do
     run_test_case ${TESTS_PATH}/"$TEST_CASE"
 done
 
diff --git a/test/cases/container-embedding.sh b/test/cases/container-embedding.sh
index 98a46fdde..120be8272 100755
--- a/test/cases/container-embedding.sh
+++ b/test/cases/container-embedding.sh
@@ -21,7 +21,7 @@ source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 # Provision the software under test.
 #
 
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 TEMPDIR=$(mktemp -d)
 function cleanup() {
diff --git a/test/cases/container-upload.sh b/test/cases/container-upload.sh
index 14fd95239..9f698c2da 100755
--- a/test/cases/container-upload.sh
+++ b/test/cases/container-upload.sh
@@ -21,7 +21,7 @@ source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 # Provision the software under test.
 #
 
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
 TEMPDIR=$(mktemp -d)
diff --git a/test/cases/cross-distro.sh b/test/cases/cross-distro.sh
index 20612c384..9c9cc2a89 100755
--- a/test/cases/cross-distro.sh
+++ b/test/cases/cross-distro.sh
@@ -46,7 +46,7 @@ esac
 
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 echo "====> Finished Provisioning system"
 echo "====> Starting $(basename "$0")"
 
diff --git a/test/cases/filesystem.sh b/test/cases/filesystem.sh
index b3625df69..1cb86529f 100644
--- a/test/cases/filesystem.sh
+++ b/test/cases/filesystem.sh
@@ -8,7 +8,7 @@ set -euo pipefail
 source /etc/os-release
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Colorful output.
 function greenprint {
diff --git a/test/cases/gcp.sh b/test/cases/gcp.sh
index 60735c311..c5ae6de02 100755
--- a/test/cases/gcp.sh
+++ b/test/cases/gcp.sh
@@ -40,7 +40,7 @@ function get_build_info() {
 CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Check available container runtime
 if which podman 2>/dev/null >&2; then
diff --git a/test/cases/generic_s3.sh b/test/cases/generic_s3.sh
index 1880a2bfd..aae481055 100755
--- a/test/cases/generic_s3.sh
+++ b/test/cases/generic_s3.sh
@@ -20,7 +20,7 @@ CONTAINER_MINIO_CLIENT="quay.io/minio/mc:latest"
 CONTAINER_MINIO_SERVER="quay.io/minio/minio:latest"
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Check available container runtime
 if which podman 2>/dev/null >&2; then
diff --git a/test/cases/image_tests.sh b/test/cases/image_tests.sh
index b87c26d80..44ddc1ebf 100755
--- a/test/cases/image_tests.sh
+++ b/test/cases/image_tests.sh
@@ -89,7 +89,7 @@ run_test_case () {
 }
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Change to the working directory.
 cd $WORKING_DIRECTORY
diff --git a/test/cases/installers.sh b/test/cases/installers.sh
index 9d8844edf..6102e7d88 100755
--- a/test/cases/installers.sh
+++ b/test/cases/installers.sh
@@ -9,7 +9,7 @@ set -euo pipefail
 #
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Get OS data.
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
diff --git a/test/cases/koji.sh b/test/cases/koji.sh
index 1c3fa1049..a7ca615ad 100755
--- a/test/cases/koji.sh
+++ b/test/cases/koji.sh
@@ -91,24 +91,34 @@ if [[ "$TEST_TYPE" == "$TEST_TYPE_CLOUD_UPLOAD" ]]; then
         set +eu
         cleanup
         sudo rm -rf "$WORKDIR"
+        /usr/libexec/osbuild-composer-test/run-mock-auth-servers.sh stop
         set -eu
     }
-    trap cleanups EXIT
 
     # install appropriate cloud environment client tool
     installClient
+else
+    # Source common functions
+    # In the common case above, this file is sourced by 'aws.sh' / 'gcp.sh' / 'azure.sh'
+    source /usr/libexec/tests/osbuild-composer/api/common/common.sh
+
+    function cleanups() {
+        greenprint "Script execution stopped or finished - Cleaning up"
+        set +eu
+        /usr/libexec/osbuild-composer-test/run-mock-auth-servers.sh stop
+        set -eu
+    }
 fi
 
+trap cleanups EXIT
+
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh jwt
 
 greenprint "Starting containers"
 sudo /usr/libexec/osbuild-composer-test/run-koji-container.sh start
 
 greenprint "Adding kerberos config"
-sudo cp \
-    /tmp/osbuild-composer-koji-test/client.keytab \
-    /etc/osbuild-composer/client.keytab
 sudo cp \
     /tmp/osbuild-composer-koji-test/client.keytab \
     /etc/osbuild-worker/client.keytab
@@ -122,10 +132,6 @@ sudo cp \
     /etc/pki/ca-trust/source/anchors/osbuild-composer-tests-ca-crt.pem
 sudo update-ca-trust
 
-greenprint "Restarting composer to pick up new config"
-sudo systemctl restart osbuild-composer
-sudo systemctl restart osbuild-worker\@1
-
 greenprint "Testing Koji"
 koji --server=http://localhost:8080/kojihub --user=osbuild --password=osbuildpass --authtype=password hello
 
@@ -156,13 +162,7 @@ esac
 if [[ "$TEST_TYPE" == "$TEST_TYPE_CLOUD_UPLOAD" ]]; then
     greenprint "Verify that image was uploaded to the cloud provider"
 
-    COMPOSE_STATUS=$(curl \
-        --silent \
-        --show-error \
-        --cacert /etc/osbuild-composer/ca-crt.pem \
-        --key /etc/osbuild-composer/client-key.pem \
-        --cert /etc/osbuild-composer/client-crt.pem \
-        "https://localhost/api/image-builder-composer/v2/composes/${COMPOSE_ID}")
+    COMPOSE_STATUS=$(compose_status "${COMPOSE_ID}")
     UPLOAD_OPTIONS=$(echo "${COMPOSE_STATUS}" | jq -r '.image_status.upload_status.options')
 
     # Authenticate with the appropriate cloud
diff --git a/test/cases/libvirt.sh b/test/cases/libvirt.sh
index 320f4f990..a53e387dc 100644
--- a/test/cases/libvirt.sh
+++ b/test/cases/libvirt.sh
@@ -9,7 +9,7 @@ set -euo pipefail
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Test the images
 /usr/libexec/osbuild-composer-test/libvirt_test.sh qcow2
diff --git a/test/cases/manifest_tests.sh b/test/cases/manifest_tests.sh
index 62ad8c405..91f6e8279 100755
--- a/test/cases/manifest_tests.sh
+++ b/test/cases/manifest_tests.sh
@@ -14,7 +14,7 @@ test_divider () {
 }
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Change to the working directory.
 cd $WORKING_DIRECTORY
diff --git a/test/cases/multi-tenancy.sh b/test/cases/multi-tenancy.sh
index 54bc9bfef..7982b0558 100755
--- a/test/cases/multi-tenancy.sh
+++ b/test/cases/multi-tenancy.sh
@@ -21,7 +21,10 @@ function greenprint {
 }
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh jwt
+
+# Source common functions
+source /usr/libexec/tests/osbuild-composer/api/common/common.sh
 
 greenprint "Registering clean ups"
 KILL_PIDS=()
@@ -39,6 +42,9 @@ function cleanup() {
   for P in "${KILL_PIDS[@]}"; do
       sudo pkill -P "$P"
   done
+
+  /usr/libexec/osbuild-composer-test/run-mock-auth-servers.sh stop
+
   set -eu
 }
 trap cleanup EXIT
@@ -61,59 +67,6 @@ sudo cp \
     "${OSBUILD_COMPOSER_TEST_DATA}"/kerberos/krb5-local.conf \
     /etc/krb5.conf.d/local
 
-greenprint "Configuring composer and worker"
-sudo tee "/etc/osbuild-composer/osbuild-composer.toml" >/dev/null <<EOF
-[koji]
-enable_tls = false
-enable_mtls = false
-enable_jwt = true
-jwt_keys_urls = ["https://localhost:8082/certs"]
-jwt_ca_file = "/etc/osbuild-composer/ca-crt.pem"
-jwt_acl_file = ""
-jwt_tenant_provider_fields = ["rh-org-id"]
-[worker]
-enable_artifacts = false
-enable_tls = true
-enable_mtls = false
-enable_jwt = true
-jwt_keys_urls = ["https://localhost:8082/certs"]
-jwt_ca_file = "/etc/osbuild-composer/ca-crt.pem"
-jwt_tenant_provider_fields = ["rh-org-id"]
-EOF
-
-# No compose will get this orgID
-sudo tee "/etc/osbuild-worker/token" >/dev/null <<EOF
-123456789
-EOF
-
-sudo tee "/etc/osbuild-worker/osbuild-worker.toml" >/dev/null <<EOF
-[authentication]
-oauth_url = "http://localhost:8081/token"
-client_id = "rhsm-api"
-offline_token = "/etc/osbuild-worker/token"
-
-[koji.localhost.kerberos]
-principal = "osbuild-krb@LOCAL"
-keytab = "/etc/osbuild-worker/client.keytab"
-
-[aws]
-bucket = "${AWS_BUCKET}"
-credentials = "/etc/osbuild-worker/aws-credentials.toml"
-EOF
-
-greenprint "Starting mock servers"
-# Spin up an https instance for the composer-api and worker-api; the auth handler needs to hit an ssl `/certs` endpoint
-sudo /usr/libexec/osbuild-composer-test/osbuild-mock-openid-provider -a localhost:8082 -rsaPubPem /etc/osbuild-composer/client-crt.pem -rsaPem /etc/osbuild-composer/client-key.pem -cert /etc/osbuild-composer/composer-crt.pem -key /etc/osbuild-composer/composer-key.pem &
-KILL_PIDS+=("$!")
-# Spin up an http instance for the worker client to bypass the need to specify an extra CA
-sudo /usr/libexec/osbuild-composer-test/osbuild-mock-openid-provider -a localhost:8081 -rsaPubPem /etc/osbuild-composer/client-crt.pem -rsaPem /etc/osbuild-composer/client-key.pem &
-KILL_PIDS+=("$!")
-
-greenprint "Restarting composer, stopping a local worker and starting a remote worker"
-sudo systemctl restart osbuild-composer
-sudo systemctl stop osbuild-worker@1
-sudo systemctl start osbuild-remote-worker@localhost:8700
-
 DISTRO=rhel-86
 
 function s3_request {
@@ -153,17 +106,6 @@ function koji_request {
 EOF
 }
 
-function access_token {
-  local refresh_token="$1"
-  curl --request POST \
-    --data "refresh_token=$refresh_token" \
-    --header "Content-Type: application/x-www-form-urlencoded" \
-    --silent \
-    --show-error \
-    --fail \
-    localhost:8081/token | jq -r .access_token
-}
-
 function send_compose {
   local request_file="$1"
   local refresh_token="$2"
@@ -172,28 +114,17 @@ function send_compose {
     --show-error \
     --fail \
     --header 'Content-Type: application/json' \
-    --header "Authorization: Bearer $(access_token "$refresh_token")" \
+    --header "Authorization: Bearer $(access_token_with_org_id "$refresh_token")" \
     --request POST \
     --data @"$request_file" \
     http://localhost:443/api/image-builder-composer/v2/compose | jq -r '.id'
 }
 
-function compose_status {
-  local compose="$1"
-  local refresh_token="$2"
-  curl \
-    --silent \
-    --show-error \
-    --fail \
-    --header "Authorization: Bearer $(access_token "$refresh_token")" \
-    "http://localhost:443/api/image-builder-composer/v2/composes/$compose" | jq -r '.status'
-}
-
 function assert_status {
   local compose="$1"
   local refresh_token="$2"
   local status="$3"
-  [[ $(compose_status "$compose" "$refresh_token") == "$status" ]]
+  [[ $(compose_status_with_org_id "$compose" "$refresh_token" | jq -r '.status') == "$status" ]]
 }
 
 function wait_for_status {
@@ -203,7 +134,7 @@ function wait_for_status {
   while true
   do
     local current_status
-    current_status=$(compose_status "$compose" "$refresh_token")
+    current_status=$(compose_status_with_org_id "$compose" "$refresh_token" | jq -r '.status')
 
     case "$current_status" in
       "$desired_status")
diff --git a/test/cases/ostree-ng.sh b/test/cases/ostree-ng.sh
index 2b9db8f09..e6ec12720 100755
--- a/test/cases/ostree-ng.sh
+++ b/test/cases/ostree-ng.sh
@@ -2,7 +2,7 @@
 set -euo pipefail
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Get OS data.
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
diff --git a/test/cases/ostree-raw-image.sh b/test/cases/ostree-raw-image.sh
index ed787c0a0..e1f0daad3 100755
--- a/test/cases/ostree-raw-image.sh
+++ b/test/cases/ostree-raw-image.sh
@@ -2,7 +2,7 @@
 set -euo pipefail
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Get OS data.
 source /etc/os-release
diff --git a/test/cases/ostree-rebase.sh b/test/cases/ostree-rebase.sh
index 147796f78..c6441309c 100755
--- a/test/cases/ostree-rebase.sh
+++ b/test/cases/ostree-rebase.sh
@@ -10,7 +10,7 @@ if [[ ${ID} == "rhel" ]] && ! sudo subscription-manager status; then
 fi
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Colorful output.
 function greenprint {
diff --git a/test/cases/ostree-simplified-installer.sh b/test/cases/ostree-simplified-installer.sh
index f7e790c8c..774853c19 100755
--- a/test/cases/ostree-simplified-installer.sh
+++ b/test/cases/ostree-simplified-installer.sh
@@ -2,7 +2,7 @@
 set -euo pipefail
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Get OS data.
 source /etc/os-release
diff --git a/test/cases/ostree.sh b/test/cases/ostree.sh
index 25e8d2ee6..39465e736 100755
--- a/test/cases/ostree.sh
+++ b/test/cases/ostree.sh
@@ -10,7 +10,7 @@ if [[ ${ID} == "rhel" ]] && ! sudo subscription-manager status; then
 fi
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Set os-variant and boot location used by virt-install.
 case "${ID}-${VERSION_ID}" in
diff --git a/test/cases/regression-bigiso.sh b/test/cases/regression-bigiso.sh
index 1e7a77746..a0d31396b 100644
--- a/test/cases/regression-bigiso.sh
+++ b/test/cases/regression-bigiso.sh
@@ -31,7 +31,7 @@ function get_build_info() {
 }
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 BLUEPRINT_FILE=/tmp/blueprint.toml
 COMPOSE_START=/tmp/compose-start.json
 COMPOSE_INFO=/tmp/compose-info.json
diff --git a/test/cases/regression-composer-works-behind-satellite-fallback.sh b/test/cases/regression-composer-works-behind-satellite-fallback.sh
index 53c2cf26f..51f880f58 100644
--- a/test/cases/regression-composer-works-behind-satellite-fallback.sh
+++ b/test/cases/regression-composer-works-behind-satellite-fallback.sh
@@ -47,7 +47,7 @@ if [[ "$ID" == rhel ]] && sudo subscription-manager status; then
 fi
 
 # Provision the software under tet.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Discover what system is installed on the runner
 case "${ID}" in
diff --git a/test/cases/regression-composer-works-behind-satellite.sh b/test/cases/regression-composer-works-behind-satellite.sh
index 1623f2682..7930a0ab1 100644
--- a/test/cases/regression-composer-works-behind-satellite.sh
+++ b/test/cases/regression-composer-works-behind-satellite.sh
@@ -76,7 +76,7 @@ if [[ "$ID" == rhel ]] && sudo subscription-manager status; then
 fi
 
 # Provision the software under tet.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Discover what system is installed on the runner
 case "${ID}" in
diff --git a/test/cases/regression-excluded-dependency.sh b/test/cases/regression-excluded-dependency.sh
index 94d6fae40..88ebf6185 100644
--- a/test/cases/regression-excluded-dependency.sh
+++ b/test/cases/regression-excluded-dependency.sh
@@ -36,7 +36,7 @@ function get_build_info() {
 }
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 BLUEPRINT_FILE=/tmp/blueprint.toml
 COMPOSE_START=/tmp/compose-start.json
 COMPOSE_INFO=/tmp/compose-info.json
diff --git a/test/cases/regression-include-excluded-packages.sh b/test/cases/regression-include-excluded-packages.sh
index 83829d28d..46e5a0fa8 100644
--- a/test/cases/regression-include-excluded-packages.sh
+++ b/test/cases/regression-include-excluded-packages.sh
@@ -22,7 +22,7 @@ if [[ "${ID}" == "fedora" ]]; then
 fi
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 BLUEPRINT_FILE=/tmp/blueprint.toml
 COMPOSE_START=/tmp/compose-start.json
 COMPOSE_INFO=/tmp/compose-info.json
diff --git a/test/cases/regression-insecure-repo.sh b/test/cases/regression-insecure-repo.sh
index 4c040be7f..0eef2608e 100644
--- a/test/cases/regression-insecure-repo.sh
+++ b/test/cases/regression-insecure-repo.sh
@@ -13,7 +13,7 @@ function greenprint {
 source /usr/libexec/osbuild-composer-test/set-env-variables.sh
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 greenprint "Registering clean ups"
 kill_pids=()
diff --git a/test/cases/regression-no-explicit-rootfs-definition.sh b/test/cases/regression-no-explicit-rootfs-definition.sh
index 84714cb7e..5b7fdbaaf 100644
--- a/test/cases/regression-no-explicit-rootfs-definition.sh
+++ b/test/cases/regression-no-explicit-rootfs-definition.sh
@@ -17,7 +17,7 @@ esac
 set -xeuo pipefail
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 
 function get_build_info() {
diff --git a/test/cases/vmware.sh b/test/cases/vmware.sh
index 3fda494c4..ce33d4779 100755
--- a/test/cases/vmware.sh
+++ b/test/cases/vmware.sh
@@ -32,7 +32,7 @@ if [ "$ID" != "rhel" ]; then
 fi
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 GOVC_CMD=/tmp/govc
 
diff --git a/test/data/composer/osbuild-composer-jwt.toml b/test/data/composer/osbuild-composer-jwt.toml
new file mode 100644
index 000000000..24b6364df
--- /dev/null
+++ b/test/data/composer/osbuild-composer-jwt.toml
@@ -0,0 +1,17 @@
+[koji]
+enable_tls = false
+enable_mtls = false
+enable_jwt = true
+jwt_keys_urls = ["https://localhost:8082/certs"]
+jwt_ca_file = "/etc/osbuild-composer/ca-crt.pem"
+jwt_acl_file = ""
+jwt_tenant_provider_fields = ["rh-org-id"]
+
+[worker]
+enable_artifacts = false
+enable_tls = true
+enable_mtls = false
+enable_jwt = true
+jwt_keys_urls = ["https://localhost:8082/certs"]
+jwt_ca_file = "/etc/osbuild-composer/ca-crt.pem"
+jwt_tenant_provider_fields = ["rh-org-id"]
diff --git a/test/data/composer/osbuild-composer.toml b/test/data/composer/osbuild-composer-tls.toml
similarity index 100%
rename from test/data/composer/osbuild-composer.toml
rename to test/data/composer/osbuild-composer-tls.toml
diff --git a/test/data/worker/osbuild-worker-jwt.toml b/test/data/worker/osbuild-worker-jwt.toml
new file mode 100644
index 000000000..1aee20311
--- /dev/null
+++ b/test/data/worker/osbuild-worker-jwt.toml
@@ -0,0 +1,8 @@
+[authentication]
+oauth_url = "http://localhost:8081/token"
+client_id = "rhsm-api"
+offline_token = "/etc/osbuild-worker/token"
+
+[koji.localhost.kerberos]
+principal = "osbuild-krb@LOCAL"
+keytab = "/etc/osbuild-worker/client.keytab"
diff --git a/test/data/worker/osbuild-worker.toml b/test/data/worker/osbuild-worker-tls.toml
similarity index 100%
rename from test/data/worker/osbuild-worker.toml
rename to test/data/worker/osbuild-worker-tls.toml
diff --git a/tools/generic_s3_test.sh b/tools/generic_s3_test.sh
index 0dbd0da2c..a4fa3d9f8 100755
--- a/tools/generic_s3_test.sh
+++ b/tools/generic_s3_test.sh
@@ -27,7 +27,7 @@ CONTAINER_MINIO_SERVER="quay.io/minio/minio:latest"
 CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"
 
 # Provision the software under test.
-/usr/libexec/osbuild-composer-test/provision.sh
+/usr/libexec/osbuild-composer-test/provision.sh none
 
 # Check available container runtime
 if which podman 2>/dev/null >&2; then