Repository: Azure/ARO-RP
Pull request #2195 (2022-06-21 08:08:00)

[M6] Generate database UUIDs in a way that can be deterministic for testing
### Which issue this PR addresses:

splitting out of a followup to #2165

### What this PR does / why we need it:

Moves database-related UUID generation into the database layer, where it can then be defined in a consistent manner for testing.

In testing, each database's UUIDs are namespaced where the first 14 bytes are a digit that corresponds with it, and then an incrementing counter.

This PR doesn't touch many tests that could use this functionality, but it's very useful for the followup to #2165 so I decided to make it consistently available.

### Test plan for issue:

mostly tests

### Is there any documentation that needs to be updated for this PR?

N/A

hawkowl: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
facchettos: @petrkotas We cannot change the naming, because these are needed to implement the `gofr/uuid` interface. 
hawkowl: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
s-amann: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
github-actions[bot]: Please rebase pull request.

Patch:
diff --git a/cmd/aro/portal.go b/cmd/aro/portal.go
index 3f4a318b7..864a082e1 100644
--- a/cmd/aro/portal.go
+++ b/cmd/aro/portal.go
@@ -11,7 +11,6 @@ import (
 	"os"
 	"strings"
 
-	"github.com/gofrs/uuid"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/database"
@@ -23,6 +22,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/encryption"
 	"github.com/Azure/ARO-RP/pkg/util/keyvault"
 	"github.com/Azure/ARO-RP/pkg/util/oidc"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 func portal(ctx context.Context, log *logrus.Entry, audit *logrus.Entry) error {
diff --git a/hack/validate-imports/allowed-import-names.yaml b/hack/validate-imports/allowed-import-names.yaml
index 9704c5aab..93c7e31b0 100644
--- a/hack/validate-imports/allowed-import-names.yaml
+++ b/hack/validate-imports/allowed-import-names.yaml
@@ -109,3 +109,5 @@ allowedImportNames:
     - hivefake
   github.com/openshift/hive/apis/hive/v1:
     - hivev1
+  github.com/gofrs/uuid:
+    - gofrsuuid
diff --git a/pkg/database/asyncoperations.go b/pkg/database/asyncoperations.go
index d12dd7e99..47523b7f1 100644
--- a/pkg/database/asyncoperations.go
+++ b/pkg/database/asyncoperations.go
@@ -11,10 +11,12 @@ import (
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type asyncOperations struct {
-	c cosmosdb.AsyncOperationDocumentClient
+	c             cosmosdb.AsyncOperationDocumentClient
+	uuidGenerator uuid.Generator
 }
 
 // AsyncOperations is the database interface for AsyncOperationDocuments
@@ -22,6 +24,7 @@ type AsyncOperations interface {
 	Create(context.Context, *api.AsyncOperationDocument) (*api.AsyncOperationDocument, error)
 	Get(context.Context, string) (*api.AsyncOperationDocument, error)
 	Patch(context.Context, string, func(*api.AsyncOperationDocument) error) (*api.AsyncOperationDocument, error)
+	NewUUID() string
 }
 
 // NewAsyncOperations returns a new AsyncOperations
@@ -33,15 +36,20 @@ func NewAsyncOperations(ctx context.Context, isLocalDevelopmentMode bool, dbc co
 
 	collc := cosmosdb.NewCollectionClient(dbc, dbid)
 	client := cosmosdb.NewAsyncOperationDocumentClient(collc, collAsyncOperations)
-	return NewAsyncOperationsWithProvidedClient(client), nil
+	return NewAsyncOperationsWithProvidedClient(client, uuid.DefaultGenerator), nil
 }
 
-func NewAsyncOperationsWithProvidedClient(client cosmosdb.AsyncOperationDocumentClient) AsyncOperations {
+func NewAsyncOperationsWithProvidedClient(client cosmosdb.AsyncOperationDocumentClient, uuidGenerator uuid.Generator) AsyncOperations {
 	return &asyncOperations{
-		c: client,
+		c:             client,
+		uuidGenerator: uuidGenerator,
 	}
 }
 
+func (c *asyncOperations) NewUUID() string {
+	return c.uuidGenerator.Generate()
+}
+
 func (c *asyncOperations) Create(ctx context.Context, doc *api.AsyncOperationDocument) (*api.AsyncOperationDocument, error) {
 	if doc.ID != strings.ToLower(doc.ID) {
 		return nil, fmt.Errorf("id %q is not lower case", doc.ID)
diff --git a/pkg/database/gateway.go b/pkg/database/gateway.go
index 505fa4263..27f9b25c7 100644
--- a/pkg/database/gateway.go
+++ b/pkg/database/gateway.go
@@ -10,10 +10,12 @@ import (
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type gateway struct {
-	c cosmosdb.GatewayDocumentClient
+	c             cosmosdb.GatewayDocumentClient
+	uuidGenerator uuid.Generator
 }
 
 type Gateway interface {
@@ -22,6 +24,7 @@ type Gateway interface {
 	Delete(context.Context, *api.GatewayDocument) error
 	Get(context.Context, string) (*api.GatewayDocument, error)
 	Patch(context.Context, string, func(*api.GatewayDocument) error) (*api.GatewayDocument, error)
+	NewUUID() string
 }
 
 func NewGateway(ctx context.Context, isDevelopmentMode bool, dbc cosmosdb.DatabaseClient) (Gateway, error) {
@@ -33,15 +36,20 @@ func NewGateway(ctx context.Context, isDevelopmentMode bool, dbc cosmosdb.Databa
 	collc := cosmosdb.NewCollectionClient(dbc, dbid)
 
 	documentClient := cosmosdb.NewGatewayDocumentClient(collc, collGateway)
-	return NewGatewayWithProvidedClient(documentClient), nil
+	return NewGatewayWithProvidedClient(documentClient, uuid.DefaultGenerator), nil
 }
 
-func NewGatewayWithProvidedClient(client cosmosdb.GatewayDocumentClient) Gateway {
+func NewGatewayWithProvidedClient(client cosmosdb.GatewayDocumentClient, uuidGenerator uuid.Generator) Gateway {
 	return &gateway{
-		c: client,
+		c:             client,
+		uuidGenerator: uuidGenerator,
 	}
 }
 
+func (c *gateway) NewUUID() string {
+	return c.uuidGenerator.Generate()
+}
+
 func (c *gateway) ChangeFeed() cosmosdb.GatewayDocumentIterator {
 	return c.c.ChangeFeed(nil)
 }
diff --git a/pkg/database/generate.go b/pkg/database/generate.go
deleted file mode 100644
index 8bf7fb110..000000000
--- a/pkg/database/generate.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package database
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-//go:generate go run ../../vendor/github.com/golang/mock/mockgen -destination=../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/$GOPACKAGE Gateway
-//go:generate go run ../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../util/mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/database/monitors.go b/pkg/database/monitors.go
index 3cd782b47..58cd3a37b 100644
--- a/pkg/database/monitors.go
+++ b/pkg/database/monitors.go
@@ -9,10 +9,9 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/gofrs/uuid"
-
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type monitors struct {
@@ -64,7 +63,7 @@ func NewMonitors(ctx context.Context, isLocalDevelopmentMode bool, dbc cosmosdb.
 
 	return &monitors{
 		c:    cosmosdb.NewMonitorDocumentClient(collc, collMonitors),
-		uuid: uuid.Must(uuid.NewV4()).String(),
+		uuid: uuid.DefaultGenerator.Generate(),
 	}, nil
 }
 
diff --git a/pkg/database/openshiftclusters.go b/pkg/database/openshiftclusters.go
index 930a2ca78..1f29eb0a2 100644
--- a/pkg/database/openshiftclusters.go
+++ b/pkg/database/openshiftclusters.go
@@ -10,10 +10,10 @@ import (
 	"strings"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 const (
@@ -28,9 +28,10 @@ const (
 type OpenShiftClusterDocumentMutator func(*api.OpenShiftClusterDocument) error
 
 type openShiftClusters struct {
-	c     cosmosdb.OpenShiftClusterDocumentClient
-	collc cosmosdb.CollectionClient
-	uuid  string
+	c             cosmosdb.OpenShiftClusterDocumentClient
+	collc         cosmosdb.CollectionClient
+	uuid          string
+	uuidGenerator uuid.Generator
 }
 
 // OpenShiftClusters is the database interface for OpenShiftClusterDocuments
@@ -51,6 +52,7 @@ type OpenShiftClusters interface {
 	EndLease(context.Context, string, api.ProvisioningState, api.ProvisioningState, *string) (*api.OpenShiftClusterDocument, error)
 	GetByClientID(ctx context.Context, partitionKey, clientID string) (*api.OpenShiftClusterDocuments, error)
 	GetByClusterResourceGroupID(ctx context.Context, partitionKey, resourceGroupID string) (*api.OpenShiftClusterDocuments, error)
+	NewUUID() string
 }
 
 // NewOpenShiftClusters returns a new OpenShiftClusters
@@ -86,17 +88,22 @@ func NewOpenShiftClusters(ctx context.Context, isLocalDevelopmentMode bool, dbc
 	}
 
 	documentClient := cosmosdb.NewOpenShiftClusterDocumentClient(collc, collOpenShiftClusters)
-	return NewOpenShiftClustersWithProvidedClient(documentClient, collc, uuid.Must(uuid.NewV4()).String()), nil
+	return NewOpenShiftClustersWithProvidedClient(documentClient, collc, uuid.DefaultGenerator.Generate(), uuid.DefaultGenerator), nil
 }
 
-func NewOpenShiftClustersWithProvidedClient(client cosmosdb.OpenShiftClusterDocumentClient, collectionClient cosmosdb.CollectionClient, uuid string) OpenShiftClusters {
+func NewOpenShiftClustersWithProvidedClient(client cosmosdb.OpenShiftClusterDocumentClient, collectionClient cosmosdb.CollectionClient, uuid string, uuidGenerator uuid.Generator) OpenShiftClusters {
 	return &openShiftClusters{
-		c:     client,
-		collc: collectionClient,
-		uuid:  uuid,
+		c:             client,
+		collc:         collectionClient,
+		uuid:          uuid,
+		uuidGenerator: uuidGenerator,
 	}
 }
 
+func (c *openShiftClusters) NewUUID() string {
+	return c.uuidGenerator.Generate()
+}
+
 func (c *openShiftClusters) Create(ctx context.Context, doc *api.OpenShiftClusterDocument) (*api.OpenShiftClusterDocument, error) {
 	if doc.Key != strings.ToLower(doc.Key) {
 		return nil, fmt.Errorf("key %q is not lower case", doc.Key)
diff --git a/pkg/database/portal.go b/pkg/database/portal.go
index 4412216de..9d598e222 100644
--- a/pkg/database/portal.go
+++ b/pkg/database/portal.go
@@ -11,10 +11,12 @@ import (
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type portals struct {
-	c cosmosdb.PortalDocumentClient
+	c             cosmosdb.PortalDocumentClient
+	uuidGenerator uuid.Generator
 }
 
 // Portal is the database interface for PortalDocuments
@@ -22,6 +24,7 @@ type Portal interface {
 	Create(context.Context, *api.PortalDocument) (*api.PortalDocument, error)
 	Get(context.Context, string) (*api.PortalDocument, error)
 	Patch(context.Context, string, func(*api.PortalDocument) error) (*api.PortalDocument, error)
+	NewUUID() string
 }
 
 // NewPortal returns a new Portal
@@ -34,15 +37,20 @@ func NewPortal(ctx context.Context, isLocalDevelopmentMode bool, dbc cosmosdb.Da
 	collc := cosmosdb.NewCollectionClient(dbc, dbid)
 
 	documentClient := cosmosdb.NewPortalDocumentClient(collc, collPortal)
-	return NewPortalWithProvidedClient(documentClient), nil
+	return NewPortalWithProvidedClient(documentClient, uuid.DefaultGenerator), nil
 }
 
-func NewPortalWithProvidedClient(client cosmosdb.PortalDocumentClient) Portal {
+func NewPortalWithProvidedClient(client cosmosdb.PortalDocumentClient, uuidGenerator uuid.Generator) Portal {
 	return &portals{
-		c: client,
+		c:             client,
+		uuidGenerator: uuidGenerator,
 	}
 }
 
+func (c *portals) NewUUID() string {
+	return c.uuidGenerator.Generate()
+}
+
 func (c *portals) Create(ctx context.Context, doc *api.PortalDocument) (*api.PortalDocument, error) {
 	if doc.ID != strings.ToLower(doc.ID) {
 		return nil, fmt.Errorf("id %q is not lower case", doc.ID)
diff --git a/pkg/database/subscriptions.go b/pkg/database/subscriptions.go
index 616d0ac15..88bb89806 100644
--- a/pkg/database/subscriptions.go
+++ b/pkg/database/subscriptions.go
@@ -9,10 +9,9 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/gofrs/uuid"
-
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 const SubscriptionsDequeueQuery string = `SELECT * FROM Subscriptions doc WHERE (doc.deleting ?? false) AND (doc.leaseExpires ?? 0) < GetCurrentTimestamp() / 1000`
@@ -78,7 +77,7 @@ func NewSubscriptions(ctx context.Context, isLocalDevelopmentMode bool, dbc cosm
 	}
 
 	documentClient := cosmosdb.NewSubscriptionDocumentClient(collc, collSubscriptions)
-	return NewSubscriptionsWithProvidedClient(documentClient, uuid.Must(uuid.NewV4()).String()), nil
+	return NewSubscriptionsWithProvidedClient(documentClient, uuid.DefaultGenerator.Generate()), nil
 }
 
 func NewSubscriptionsWithProvidedClient(client cosmosdb.SubscriptionDocumentClient, uuid string) Subscriptions {
diff --git a/pkg/dbtoken/server.go b/pkg/dbtoken/server.go
index 3ce2fe9b8..3f321fa99 100644
--- a/pkg/dbtoken/server.go
+++ b/pkg/dbtoken/server.go
@@ -16,7 +16,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 
@@ -26,6 +25,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/portal/middleware"
 	"github.com/Azure/ARO-RP/pkg/util/heartbeat"
 	"github.com/Azure/ARO-RP/pkg/util/oidc"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 var rxValidPermission = regexp.MustCompile("^[a-z]{1,20}$")
@@ -146,7 +146,7 @@ func (s *server) authenticate(h http.Handler) http.Handler {
 			return
 		}
 
-		if _, err := uuid.FromString(token.Subject()); err != nil {
+		if valid := uuid.IsValid(token.Subject()); !valid {
 			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
 			return
 		}
diff --git a/pkg/deploy/generator/templates.go b/pkg/deploy/generator/templates.go
index 452ff16a0..aede2b818 100644
--- a/pkg/deploy/generator/templates.go
+++ b/pkg/deploy/generator/templates.go
@@ -8,9 +8,8 @@ import (
 	"encoding/json"
 	"regexp"
 
-	"github.com/gofrs/uuid"
-
 	"github.com/Azure/ARO-RP/pkg/util/arm"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 const (
@@ -23,7 +22,7 @@ const (
 )
 
 var (
-	tenantUUIDHack = uuid.Must(uuid.FromString(tenantIDHack))
+	tenantUUIDHack = uuid.MustFromString(tenantIDHack)
 )
 
 func max(is ...int) int {
diff --git a/pkg/env/armhelper.go b/pkg/env/armhelper.go
index fdf85b1ad..2ff006206 100644
--- a/pkg/env/armhelper.go
+++ b/pkg/env/armhelper.go
@@ -13,13 +13,13 @@ import (
 	"github.com/Azure/go-autorest/autorest/azure"
 	"github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/Azure/go-autorest/autorest/to"
-	"github.com/gofrs/uuid"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/graphrbac"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization"
 	"github.com/Azure/ARO-RP/pkg/util/rbac"
 	"github.com/Azure/ARO-RP/pkg/util/refreshable"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 // In INT or PROD, when the ARO RP is running behind ARM, ARM follows the RP's
@@ -131,7 +131,7 @@ func (ah *armHelper) EnsureARMResourceGroupRoleAssignment(ctx context.Context, f
 		return err
 	}
 
-	_, err = ah.roleassignments.Create(ctx, "/subscriptions/"+ah.env.SubscriptionID()+"/resourceGroups/"+resourceGroup, uuid.Must(uuid.NewV4()).String(), mgmtauthorization.RoleAssignmentCreateParameters{
+	_, err = ah.roleassignments.Create(ctx, "/subscriptions/"+ah.env.SubscriptionID()+"/resourceGroups/"+resourceGroup, uuid.DefaultGenerator.Generate(), mgmtauthorization.RoleAssignmentCreateParameters{
 		RoleAssignmentProperties: &mgmtauthorization.RoleAssignmentProperties{
 			RoleDefinitionID: to.StringPtr("/subscriptions/" + ah.env.SubscriptionID() + "/providers/Microsoft.Authorization/roleDefinitions/" + rbac.RoleOwner),
 			PrincipalID:      res.Value,
diff --git a/pkg/frontend/asyncoperations.go b/pkg/frontend/asyncoperations.go
index e1780d5f9..d4aa1f4ce 100644
--- a/pkg/frontend/asyncoperations.go
+++ b/pkg/frontend/asyncoperations.go
@@ -9,14 +9,13 @@ import (
 	"strings"
 	"time"
 
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 )
 
 func (f *frontend) newAsyncOperation(ctx context.Context, r *http.Request, doc *api.OpenShiftClusterDocument) (string, error) {
-	id := uuid.Must(uuid.NewV4()).String()
+	id := f.dbAsyncOperations.NewUUID()
 	_, err := f.dbAsyncOperations.Create(ctx, &api.AsyncOperationDocument{
 		ID:                  id,
 		OpenShiftClusterKey: doc.Key,
diff --git a/pkg/frontend/middleware/log.go b/pkg/frontend/middleware/log.go
index 9a89246be..4e5fe372e 100644
--- a/pkg/frontend/middleware/log.go
+++ b/pkg/frontend/middleware/log.go
@@ -11,7 +11,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 
@@ -20,6 +19,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/env"
 	utillog "github.com/Azure/ARO-RP/pkg/util/log"
 	"github.com/Azure/ARO-RP/pkg/util/log/audit"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type logResponseWriter struct {
@@ -65,7 +65,7 @@ func Log(env env.Core, auditLog, baseLog *logrus.Entry) func(http.Handler) http.
 			correlationData := &api.CorrelationData{
 				ClientRequestID: r.Header.Get("X-Ms-Client-Request-Id"),
 				CorrelationID:   r.Header.Get("X-Ms-Correlation-Request-Id"),
-				RequestID:       uuid.Must(uuid.NewV4()).String(),
+				RequestID:       uuid.DefaultGenerator.Generate(),
 				RequestTime:     t,
 			}
 
diff --git a/pkg/frontend/middleware/validate.go b/pkg/frontend/middleware/validate.go
index 661489548..6915e51ca 100644
--- a/pkg/frontend/middleware/validate.go
+++ b/pkg/frontend/middleware/validate.go
@@ -8,12 +8,12 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/env"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 const (
@@ -46,8 +46,8 @@ func Validate(env env.Core, apis map[string]*api.Version) func(http.Handler) htt
 			}
 
 			if _, found := vars["subscriptionId"]; found {
-				_, err := uuid.FromString(vars["subscriptionId"])
-				if err != nil {
+				valid := uuid.IsValid(vars["subscriptionId"])
+				if !valid {
 					api.WriteError(w, http.StatusBadRequest, api.CloudErrorCodeInvalidSubscriptionID, "", "The provided subscription identifier '%s' is malformed or invalid.", vars["subscriptionId"])
 					return
 				}
@@ -89,8 +89,8 @@ func Validate(env env.Core, apis map[string]*api.Version) func(http.Handler) htt
 			}
 
 			if _, found := vars["operationId"]; found {
-				_, err := uuid.FromString(vars["operationId"])
-				if err != nil {
+				valid := uuid.IsValid(vars["operationId"])
+				if !valid {
 					api.WriteError(w, http.StatusBadRequest, api.CloudErrorCodeInvalidOperationID, "", "The provided operation identifier '%s' is malformed or invalid.", vars["operationId"])
 					return
 				}
diff --git a/pkg/frontend/openshiftcluster_putorpatch.go b/pkg/frontend/openshiftcluster_putorpatch.go
index 29a336614..b9d0f9848 100644
--- a/pkg/frontend/openshiftcluster_putorpatch.go
+++ b/pkg/frontend/openshiftcluster_putorpatch.go
@@ -13,7 +13,6 @@ import (
 	"time"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 
@@ -66,7 +65,7 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, log *logrus.
 		}
 
 		doc = &api.OpenShiftClusterDocument{
-			ID:  uuid.Must(uuid.NewV4()).String(),
+			ID:  f.dbOpenShiftClusters.NewUUID(),
 			Key: r.URL.Path,
 			OpenShiftCluster: &api.OpenShiftCluster{
 				ID:   originalPath,
diff --git a/pkg/hive/manager.go b/pkg/hive/manager.go
index 93021d5a7..d5088f553 100644
--- a/pkg/hive/manager.go
+++ b/pkg/hive/manager.go
@@ -6,7 +6,6 @@ package hive
 import (
 	"context"
 
-	"github.com/gofrs/uuid"
 	hiveclient "github.com/openshift/hive/pkg/client/clientset/versioned"
 	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
@@ -18,6 +17,7 @@ import (
 	"k8s.io/client-go/util/retry"
 
 	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type ClusterManager interface {
@@ -80,7 +80,7 @@ func (hr *clusterManager) CreateNamespace(ctx context.Context) (*corev1.Namespac
 	var namespaceName string
 	var namespace *corev1.Namespace
 	err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
-		namespaceName = "aro-" + uuid.Must(uuid.NewV4()).String()
+		namespaceName = "aro-" + uuid.DefaultGenerator.Generate()
 		namespace = &corev1.Namespace{
 			ObjectMeta: metav1.ObjectMeta{
 				Name: namespaceName,
diff --git a/pkg/portal/kubeconfig/kubeconfig.go b/pkg/portal/kubeconfig/kubeconfig.go
index 503e3980a..5d1a36b9f 100644
--- a/pkg/portal/kubeconfig/kubeconfig.go
+++ b/pkg/portal/kubeconfig/kubeconfig.go
@@ -14,7 +14,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 	clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1"
@@ -45,8 +44,6 @@ type kubeconfig struct {
 
 	dialer      proxy.Dialer
 	clientCache clientcache.ClientCache
-
-	newToken func() string
 }
 
 func New(baseLog *logrus.Entry,
@@ -72,8 +69,6 @@ func New(baseLog *logrus.Entry,
 
 		dialer:      dialer,
 		clientCache: clientcache.New(time.Hour),
-
-		newToken: func() string { return uuid.Must(uuid.NewV4()).String() },
 	}
 
 	rp := &httputil.ReverseProxy{
@@ -106,8 +101,7 @@ func (k *kubeconfig) new(w http.ResponseWriter, r *http.Request) {
 
 	elevated := len(middleware.GroupsIntersect(k.elevatedGroupIDs, ctx.Value(middleware.ContextKeyGroups).([]string))) > 0
 
-	token := k.newToken()
-
+	token := k.dbPortal.NewUUID()
 	portalDoc := &api.PortalDocument{
 		ID:  token,
 		TTL: int(kubeconfigNewTimeout / time.Second),
diff --git a/pkg/portal/middleware/aad.go b/pkg/portal/middleware/aad.go
index 29742b773..06afd5216 100644
--- a/pkg/portal/middleware/aad.go
+++ b/pkg/portal/middleware/aad.go
@@ -15,7 +15,6 @@ import (
 	"time"
 
 	"github.com/Azure/go-autorest/autorest/adal"
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/sessions"
 	"github.com/sirupsen/logrus"
@@ -24,6 +23,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/env"
 	"github.com/Azure/ARO-RP/pkg/util/oidc"
 	"github.com/Azure/ARO-RP/pkg/util/roundtripper"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 const (
@@ -209,7 +209,7 @@ func (a *aad) redirect(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	state := uuid.Must(uuid.NewV4()).String()
+	state := uuid.DefaultGenerator.Generate()
 
 	session.Values = map[interface{}]interface{}{
 		sessionKeyState: state,
diff --git a/pkg/portal/middleware/bearer.go b/pkg/portal/middleware/bearer.go
index d17d4bb8e..63d1c50e0 100644
--- a/pkg/portal/middleware/bearer.go
+++ b/pkg/portal/middleware/bearer.go
@@ -8,9 +8,8 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/gofrs/uuid"
-
 	"github.com/Azure/ARO-RP/pkg/database"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 // Bearer validates a Bearer token and adds the corresponding username to the
diff --git a/pkg/portal/ssh/proxy.go b/pkg/portal/ssh/proxy.go
index ec263003c..dcffaaa43 100644
--- a/pkg/portal/ssh/proxy.go
+++ b/pkg/portal/ssh/proxy.go
@@ -12,7 +12,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/gofrs/uuid"
 	"github.com/sirupsen/logrus"
 	cryptossh "golang.org/x/crypto/ssh"
 	"golang.org/x/crypto/ssh/agent"
@@ -21,6 +20,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/api"
 	utillog "github.com/Azure/ARO-RP/pkg/util/log"
 	"github.com/Azure/ARO-RP/pkg/util/recover"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 const (
diff --git a/pkg/portal/ssh/ssh.go b/pkg/portal/ssh/ssh.go
index cc1d59334..e4ddd30c7 100644
--- a/pkg/portal/ssh/ssh.go
+++ b/pkg/portal/ssh/ssh.go
@@ -13,7 +13,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/gofrs/uuid"
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 	cryptossh "golang.org/x/crypto/ssh"
@@ -44,7 +43,6 @@ type ssh struct {
 	dialer proxy.Dialer
 
 	baseServerConfig *cryptossh.ServerConfig
-	newPassword      func() string
 }
 
 func New(env env.Core,
@@ -71,7 +69,6 @@ func New(env env.Core,
 		dialer: dialer,
 
 		baseServerConfig: &cryptossh.ServerConfig{},
-		newPassword:      func() string { return uuid.Must(uuid.NewV4()).String() },
 	}
 
 	signer, err := cryptossh.NewSignerFromSigner(hostKey)
@@ -135,8 +132,7 @@ func (s *ssh) new(w http.ResponseWriter, r *http.Request) {
 	username := r.Context().Value(middleware.ContextKeyUsername).(string)
 	username = strings.SplitN(username, "@", 2)[0]
 
-	password := s.newPassword()
-
+	password := s.dbPortal.NewUUID()
 	portalDoc := &api.PortalDocument{
 		ID:  password,
 		TTL: int(sshNewTimeout / time.Second),
diff --git a/pkg/util/acrtoken/acrtoken.go b/pkg/util/acrtoken/acrtoken.go
index 2a62cc01d..384dc6529 100644
--- a/pkg/util/acrtoken/acrtoken.go
+++ b/pkg/util/acrtoken/acrtoken.go
@@ -12,11 +12,11 @@ import (
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/azure"
 	"github.com/Azure/go-autorest/autorest/to"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/env"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/containerregistry"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type Manager interface {
@@ -65,7 +65,7 @@ func (m *manager) GetRegistryProfile(oc *api.OpenShiftCluster) *api.RegistryProf
 func (m *manager) NewRegistryProfile(oc *api.OpenShiftCluster) *api.RegistryProfile {
 	return &api.RegistryProfile{
 		Name:     fmt.Sprintf("%s.%s", m.r.ResourceName, m.env.Environment().ContainerRegistryDNSSuffix),
-		Username: "token-" + uuid.Must(uuid.NewV4()).String(),
+		Username: "token-" + uuid.DefaultGenerator.Generate(),
 	}
 }
 
diff --git a/pkg/util/arm/marshal.go b/pkg/util/arm/marshal.go
index 6163af05e..58929d5a6 100644
--- a/pkg/util/arm/marshal.go
+++ b/pkg/util/arm/marshal.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"reflect"
 
-	"github.com/gofrs/uuid"
+	gofrsuuid "github.com/gofrs/uuid"
 )
 
 // MarshalJSON marshals the nested r.Resource ignoring any MarshalJSON() methods
@@ -86,7 +86,7 @@ func _shadowCopy(v reflect.Value) reflect.Value {
 	switch v.Kind() {
 	case reflect.Array:
 		var t reflect.Type
-		if v.Type() == reflect.TypeOf(uuid.UUID{}) {
+		if v.Type() == reflect.TypeOf(gofrsuuid.UUID{}) {
 			// keep uuid.UUID - encoding/json will detect it and marshal it into
 			// a string
 			t = v.Type()
diff --git a/pkg/util/cluster/aad.go b/pkg/util/cluster/aad.go
index bbea2a49a..e6333d68f 100644
--- a/pkg/util/cluster/aad.go
+++ b/pkg/util/cluster/aad.go
@@ -12,8 +12,9 @@ import (
 	azgraphrbac "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/date"
-	"github.com/gofrs/uuid"
 	"k8s.io/apimachinery/pkg/util/wait"
+
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 func (c *Cluster) getServicePrincipal(ctx context.Context, appID string) (string, error) {
@@ -36,7 +37,7 @@ func (c *Cluster) getServicePrincipal(ctx context.Context, appID string) (string
 }
 
 func (c *Cluster) createApplication(ctx context.Context, displayName string) (string, string, error) {
-	password := uuid.Must(uuid.NewV4()).String()
+	password := uuid.DefaultGenerator.Generate()
 
 	app, err := c.applications.Create(ctx, azgraphrbac.ApplicationCreateParameters{
 		DisplayName: &displayName,
diff --git a/pkg/util/cluster/cluster.go b/pkg/util/cluster/cluster.go
index 49ff082bd..6375d1e64 100644
--- a/pkg/util/cluster/cluster.go
+++ b/pkg/util/cluster/cluster.go
@@ -22,7 +22,6 @@ import (
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/Azure/go-autorest/autorest/to"
-	"github.com/gofrs/uuid"
 	"github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/util/wait"
 
@@ -42,6 +41,7 @@ import (
 	redhatopenshift20210901preview "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/redhatopenshift/2021-09-01-preview/redhatopenshift"
 	redhatopenshift20220401 "github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/redhatopenshift/2022-04-01/redhatopenshift"
 	"github.com/Azure/ARO-RP/pkg/util/rbac"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 type Cluster struct {
@@ -188,7 +188,7 @@ func (c *Cluster) Create(ctx context.Context, vnetResourceGroup, clusterName str
 
 	if c.ci {
 		// name is limited to 24 characters, but must be globally unique, so we generate one and try if it is available
-		kvName = "kv-" + uuid.Must(uuid.NewV4()).String()[:21]
+		kvName = "kv-" + uuid.DefaultGenerator.Generate()[:21]
 		result, err := c.vaultsClient.CheckNameAvailability(ctx, mgmtkeyvault.VaultCheckNameAvailabilityParameters{Name: &kvName, Type: to.StringPtr("Microsoft.KeyVault/vaults")})
 		if err != nil {
 			return err
@@ -259,7 +259,7 @@ func (c *Cluster) Create(ctx context.Context, vnetResourceGroup, clusterName str
 				_, err = c.roleassignments.Create(
 					ctx,
 					scope.resource,
-					uuid.Must(uuid.NewV4()).String(),
+					uuid.DefaultGenerator.Generate(),
 					mgmtauthorization.RoleAssignmentCreateParameters{
 						RoleAssignmentProperties: &mgmtauthorization.RoleAssignmentProperties{
 							RoleDefinitionID: to.StringPtr("/subscriptions/" + c.env.SubscriptionID() + "/providers/Microsoft.Authorization/roleDefinitions/" + scope.role),
diff --git a/pkg/util/log/audit/audit.go b/pkg/util/log/audit/audit.go
index 4b17b53f7..999487886 100644
--- a/pkg/util/log/audit/audit.go
+++ b/pkg/util/log/audit/audit.go
@@ -8,8 +8,9 @@ import (
 	"fmt"
 	"sync"
 
-	"github.com/gofrs/uuid"
 	"github.com/sirupsen/logrus"
+
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 )
 
 const (
@@ -61,7 +62,7 @@ var (
 	// epoch is an unique identifier associated with the current session of the
 	// telemetry library running on the platform. It must be stable during a
 	// session, and has no implied ordering across sessions.
-	epoch = uuid.Must(uuid.NewV4()).String()
+	epoch = uuid.DefaultGenerator.Generate()
 
 	// seqNum is used to track absolute order of uploaded events, per session.
 	// It is reset when the ARO component is restarted. The first log will have
diff --git a/pkg/util/mocks/database/database.go b/pkg/util/mocks/database/database.go
deleted file mode 100644
index aa7bfd28d..000000000
--- a/pkg/util/mocks/database/database.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/Azure/ARO-RP/pkg/database (interfaces: Gateway)
-
-// Package mock_database is a generated GoMock package.
-package mock_database
-
-import (
-	context "context"
-	reflect "reflect"
-
-	gomock "github.com/golang/mock/gomock"
-
-	api "github.com/Azure/ARO-RP/pkg/api"
-	cosmosdb "github.com/Azure/ARO-RP/pkg/database/cosmosdb"
-)
-
-// MockGateway is a mock of Gateway interface.
-type MockGateway struct {
-	ctrl     *gomock.Controller
-	recorder *MockGatewayMockRecorder
-}
-
-// MockGatewayMockRecorder is the mock recorder for MockGateway.
-type MockGatewayMockRecorder struct {
-	mock *MockGateway
-}
-
-// NewMockGateway creates a new mock instance.
-func NewMockGateway(ctrl *gomock.Controller) *MockGateway {
-	mock := &MockGateway{ctrl: ctrl}
-	mock.recorder = &MockGatewayMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockGateway) EXPECT() *MockGatewayMockRecorder {
-	return m.recorder
-}
-
-// ChangeFeed mocks base method.
-func (m *MockGateway) ChangeFeed() cosmosdb.GatewayDocumentIterator {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ChangeFeed")
-	ret0, _ := ret[0].(cosmosdb.GatewayDocumentIterator)
-	return ret0
-}
-
-// ChangeFeed indicates an expected call of ChangeFeed.
-func (mr *MockGatewayMockRecorder) ChangeFeed() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeFeed", reflect.TypeOf((*MockGateway)(nil).ChangeFeed))
-}
-
-// Create mocks base method.
-func (m *MockGateway) Create(arg0 context.Context, arg1 *api.GatewayDocument) (*api.GatewayDocument, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Create", arg0, arg1)
-	ret0, _ := ret[0].(*api.GatewayDocument)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// Create indicates an expected call of Create.
-func (mr *MockGatewayMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockGateway)(nil).Create), arg0, arg1)
-}
-
-// Delete mocks base method.
-func (m *MockGateway) Delete(arg0 context.Context, arg1 *api.GatewayDocument) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Delete", arg0, arg1)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Delete indicates an expected call of Delete.
-func (mr *MockGatewayMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockGateway)(nil).Delete), arg0, arg1)
-}
-
-// Get mocks base method.
-func (m *MockGateway) Get(arg0 context.Context, arg1 string) (*api.GatewayDocument, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Get", arg0, arg1)
-	ret0, _ := ret[0].(*api.GatewayDocument)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// Get indicates an expected call of Get.
-func (mr *MockGatewayMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockGateway)(nil).Get), arg0, arg1)
-}
-
-// Patch mocks base method.
-func (m *MockGateway) Patch(arg0 context.Context, arg1 string, arg2 func(*api.GatewayDocument) error) (*api.GatewayDocument, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Patch", arg0, arg1, arg2)
-	ret0, _ := ret[0].(*api.GatewayDocument)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// Patch indicates an expected call of Patch.
-func (mr *MockGatewayMockRecorder) Patch(arg0, arg1, arg2 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockGateway)(nil).Patch), arg0, arg1, arg2)
-}
diff --git a/pkg/util/uuid/uuid.go b/pkg/util/uuid/uuid.go
new file mode 100644
index 000000000..db4b3d221
--- /dev/null
+++ b/pkg/util/uuid/uuid.go
@@ -0,0 +1,33 @@
+package uuid
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	gofrsuuid "github.com/gofrs/uuid"
+)
+
+type Generator interface {
+	Generate() string
+}
+
+type defaultGenerator struct{}
+
+func (d defaultGenerator) Generate() string {
+	return gofrsuuid.Must(gofrsuuid.DefaultGenerator.NewV4()).String()
+}
+
+var DefaultGenerator Generator = defaultGenerator{}
+
+func FromString(u string) (gofrsuuid.UUID, error) {
+	return gofrsuuid.FromString(u)
+}
+
+func MustFromString(u string) gofrsuuid.UUID {
+	return gofrsuuid.Must(gofrsuuid.FromString(u))
+}
+
+func IsValid(u string) bool {
+	_, err := gofrsuuid.FromString(u)
+	return err == nil
+}


Test patch:
diff --git a/pkg/api/admin/openshiftcluster_validatestatic_test.go b/pkg/api/admin/openshiftcluster_validatestatic_test.go
index 9adfa2bd5..018a9d224 100644
--- a/pkg/api/admin/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/admin/openshiftcluster_validatestatic_test.go
@@ -10,9 +10,8 @@ import (
 	"testing"
 	"time"
 
-	"github.com/gofrs/uuid"
-
 	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/test/validate"
 )
 
@@ -322,7 +321,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 				}
 			},
 			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile.ClientID = uuid.Must(uuid.NewV4()).String()
+				oc.Properties.ServicePrincipalProfile.ClientID = uuid.DefaultGenerator.Generate()
 			},
 			wantErr: "400: PropertyChangeNotAllowed: properties.servicePrincipalProfile.clientId: Changing property 'properties.servicePrincipalProfile.clientId' is not allowed.",
 		},
@@ -338,7 +337,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 				}
 			},
 			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile.SPObjectID = uuid.Must(uuid.NewV4()).String()
+				oc.Properties.ServicePrincipalProfile.SPObjectID = uuid.DefaultGenerator.Generate()
 			},
 			wantErr: "400: PropertyChangeNotAllowed: properties.servicePrincipalProfile.spObjectId: Changing property 'properties.servicePrincipalProfile.spObjectId' is not allowed.",
 		},
diff --git a/pkg/api/v20191231preview/openshiftcluster_validatestatic.go b/pkg/api/v20191231preview/openshiftcluster_validatestatic.go
index 1bde17ad1..d798752de 100644
--- a/pkg/api/v20191231preview/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20191231preview/openshiftcluster_validatestatic.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/api/validate"
 	"github.com/Azure/ARO-RP/pkg/util/immutable"
 	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
@@ -173,8 +173,8 @@ func (sv *openShiftClusterStaticValidator) validateConsoleProfile(path string, c
 }
 
 func (sv *openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	_, err := uuid.FromString(spp.ClientID)
-	if err != nil {
+	valid := uuid.IsValid(spp.ClientID)
+	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
 	}
 	if spp.ClientSecret == "" {
diff --git a/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go b/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go
index 71a948652..539d53455 100644
--- a/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20191231preview/openshiftcluster_validatestatic_test.go
@@ -10,9 +10,9 @@ import (
 	"testing"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 	"github.com/Azure/ARO-RP/test/validate"
 )
@@ -762,7 +762,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 		{
 			name: "clientId change",
 			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile.ClientID = uuid.Must(uuid.NewV4()).String()
+				oc.Properties.ServicePrincipalProfile.ClientID = uuid.DefaultGenerator.Generate()
 			},
 		},
 		{
diff --git a/pkg/api/v20200430/openshiftcluster_validatestatic.go b/pkg/api/v20200430/openshiftcluster_validatestatic.go
index c38b26902..4d1170d6f 100644
--- a/pkg/api/v20200430/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20200430/openshiftcluster_validatestatic.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/api/validate"
 	"github.com/Azure/ARO-RP/pkg/util/immutable"
 	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
@@ -175,8 +175,8 @@ func (sv *openShiftClusterStaticValidator) validateConsoleProfile(path string, c
 }
 
 func (sv *openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	_, err := uuid.FromString(spp.ClientID)
-	if err != nil {
+	valid := uuid.IsValid(spp.ClientID)
+	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
 	}
 	if spp.ClientSecret == "" {
diff --git a/pkg/api/v20200430/openshiftcluster_validatestatic_test.go b/pkg/api/v20200430/openshiftcluster_validatestatic_test.go
index bdd6c077e..798313ffb 100644
--- a/pkg/api/v20200430/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20200430/openshiftcluster_validatestatic_test.go
@@ -10,9 +10,9 @@ import (
 	"testing"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 	"github.com/Azure/ARO-RP/test/validate"
 )
@@ -759,7 +759,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 		{
 			name: "clientId change",
 			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile.ClientID = uuid.Must(uuid.NewV4()).String()
+				oc.Properties.ServicePrincipalProfile.ClientID = uuid.DefaultGenerator.Generate()
 			},
 		},
 		{
diff --git a/pkg/api/v20210901preview/openshiftcluster_validatestatic.go b/pkg/api/v20210901preview/openshiftcluster_validatestatic.go
index b73c6837e..32cbe45d7 100644
--- a/pkg/api/v20210901preview/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20210901preview/openshiftcluster_validatestatic.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/api/validate"
 	"github.com/Azure/ARO-RP/pkg/util/immutable"
 	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
@@ -175,8 +175,8 @@ func (sv *openShiftClusterStaticValidator) validateConsoleProfile(path string, c
 }
 
 func (sv *openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	_, err := uuid.FromString(spp.ClientID)
-	if err != nil {
+	valid := uuid.IsValid(spp.ClientID)
+	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
 	}
 	if spp.ClientSecret == "" {
diff --git a/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go b/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go
index 5788b4d5f..aaf146a77 100644
--- a/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20210901preview/openshiftcluster_validatestatic_test.go
@@ -11,9 +11,9 @@ import (
 	"time"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 	"github.com/Azure/ARO-RP/test/validate"
 )
@@ -863,7 +863,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 		{
 			name: "clientId change",
 			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile.ClientID = uuid.Must(uuid.NewV4()).String()
+				oc.Properties.ServicePrincipalProfile.ClientID = uuid.DefaultGenerator.Generate()
 			},
 		},
 		{
diff --git a/pkg/api/v20220401/openshiftcluster_validatestatic.go b/pkg/api/v20220401/openshiftcluster_validatestatic.go
index 6e10a5cf4..a233cc2b7 100644
--- a/pkg/api/v20220401/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20220401/openshiftcluster_validatestatic.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/api/validate"
 	"github.com/Azure/ARO-RP/pkg/util/immutable"
 	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
@@ -181,8 +181,8 @@ func (sv *openShiftClusterStaticValidator) validateConsoleProfile(path string, c
 }
 
 func (sv *openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	_, err := uuid.FromString(spp.ClientID)
-	if err != nil {
+	valid := uuid.IsValid(spp.ClientID)
+	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
 	}
 	if spp.ClientSecret == "" {
diff --git a/pkg/api/v20220401/openshiftcluster_validatestatic_test.go b/pkg/api/v20220401/openshiftcluster_validatestatic_test.go
index f8dbe9c91..3b53fcd9e 100644
--- a/pkg/api/v20220401/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20220401/openshiftcluster_validatestatic_test.go
@@ -11,9 +11,9 @@ import (
 	"time"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 	"github.com/Azure/ARO-RP/test/validate"
 )
@@ -853,7 +853,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 		{
 			name: "clientId change",
 			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile.ClientID = uuid.Must(uuid.NewV4()).String()
+				oc.Properties.ServicePrincipalProfile.ClientID = uuid.DefaultGenerator.Generate()
 			},
 		},
 		{
diff --git a/pkg/api/v20220904/openshiftcluster_validatestatic.go b/pkg/api/v20220904/openshiftcluster_validatestatic.go
index 4ee465547..03a0dbad6 100644
--- a/pkg/api/v20220904/openshiftcluster_validatestatic.go
+++ b/pkg/api/v20220904/openshiftcluster_validatestatic.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	"github.com/Azure/go-autorest/autorest/azure"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/api/validate"
 	"github.com/Azure/ARO-RP/pkg/util/immutable"
 	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
@@ -181,8 +181,8 @@ func (sv *openShiftClusterStaticValidator) validateConsoleProfile(path string, c
 }
 
 func (sv *openShiftClusterStaticValidator) validateServicePrincipalProfile(path string, spp *ServicePrincipalProfile) error {
-	_, err := uuid.FromString(spp.ClientID)
-	if err != nil {
+	valid := uuid.IsValid(spp.ClientID)
+	if !valid {
 		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, path+".clientId", "The provided client ID '%s' is invalid.", spp.ClientID)
 	}
 	if spp.ClientSecret == "" {
diff --git a/pkg/api/v20220904/openshiftcluster_validatestatic_test.go b/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
index 6c2d55ec8..67be83ef6 100644
--- a/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
+++ b/pkg/api/v20220904/openshiftcluster_validatestatic_test.go
@@ -12,9 +12,9 @@ import (
 
 	"github.com/Azure/go-autorest/autorest/azure"
 	"github.com/Azure/go-autorest/autorest/to"
-	"github.com/gofrs/uuid"
 
 	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 	"github.com/Azure/ARO-RP/test/validate"
 )
@@ -892,7 +892,7 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) {
 		{
 			name: "clientId change",
 			modify: func(oc *OpenShiftCluster) {
-				oc.Properties.ServicePrincipalProfile.ClientID = uuid.Must(uuid.NewV4()).String()
+				oc.Properties.ServicePrincipalProfile.ClientID = uuid.DefaultGenerator.Generate()
 			},
 		},
 		{
diff --git a/pkg/gateway/gateway_test.go b/pkg/gateway/gateway_test.go
index d9af821c6..a6579767f 100644
--- a/pkg/gateway/gateway_test.go
+++ b/pkg/gateway/gateway_test.go
@@ -15,7 +15,6 @@ import (
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
-	mock_database "github.com/Azure/ARO-RP/pkg/util/mocks/database"
 	mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env"
 	mock_metrics "github.com/Azure/ARO-RP/pkg/util/mocks/metrics"
 	utilnet "github.com/Azure/ARO-RP/pkg/util/net"
@@ -99,7 +98,6 @@ func TestNewGateway(t *testing.T) {
 			defer controller.Finish()
 
 			baseLog := logrus.NewEntry(logrus.StandardLogger())
-			db := mock_database.NewMockGateway(controller)
 			metrics := mock_metrics.NewMockEmitter(controller)
 
 			httpl, _ := utilnet.Listen("tcp", ":8080", SocketSize)
@@ -108,7 +106,7 @@ func TestNewGateway(t *testing.T) {
 			env := mock_env.NewMockCore(controller)
 			tt.mocks(env)
 
-			gtwy, err := NewGateway(ctx, env, baseLog, baseLog, db, httpsl, httpl, tt.acrResourceID, tt.gatewayDomains, metrics)
+			gtwy, err := NewGateway(ctx, env, baseLog, baseLog, nil, httpsl, httpl, tt.acrResourceID, tt.gatewayDomains, metrics)
 
 			if tt.wantErr != "" {
 				if err == nil {
@@ -157,13 +155,12 @@ func TestNewGatewayDefaultConditions(t *testing.T) {
 
 	controller := gomock.NewController(t)
 	defer controller.Finish()
-	db := mock_database.NewMockGateway(controller)
 	metrics := mock_metrics.NewMockEmitter(controller)
 	env := mock_env.NewMockCore(controller)
 	env.EXPECT().Environment().AnyTimes().Return(populatedEnv)
 	env.EXPECT().Location().AnyTimes().Return("location")
 
-	gtwy, _ := NewGateway(ctx, env, baseLog, baseLog, db, httpsl, httpl, acrResourceID, gatewayDomains, metrics)
+	gtwy, _ := NewGateway(ctx, env, baseLog, baseLog, nil, httpsl, httpl, acrResourceID, gatewayDomains, metrics)
 
 	gateway, _ := gtwy.(*gateway)
 
diff --git a/pkg/portal/kubeconfig/kubeconfig_test.go b/pkg/portal/kubeconfig/kubeconfig_test.go
index 64ee04ef2..a5f553924 100644
--- a/pkg/portal/kubeconfig/kubeconfig_test.go
+++ b/pkg/portal/kubeconfig/kubeconfig_test.go
@@ -29,7 +29,7 @@ func TestNew(t *testing.T) {
 	resourceID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg/providers/microsoft.redhatopenshift/openshiftclusters/cluster"
 	elevatedGroupIDs := []string{"10000000-0000-0000-0000-000000000000"}
 	username := "username"
-	password := "password"
+	password := "03030303-0303-0303-0303-030303030001"
 
 	servingCert := &x509.Certificate{}
 
@@ -60,7 +60,7 @@ func TestNew(t *testing.T) {
 			wantHeaders: http.Header{
 				"Content-Disposition": []string{`attachment; filename="cluster.kubeconfig"`},
 			},
-			wantBody: "{\n    \"kind\": \"Config\",\n    \"apiVersion\": \"v1\",\n    \"preferences\": {},\n    \"clusters\": [\n        {\n            \"name\": \"cluster\",\n            \"cluster\": {\n                \"server\": \"https://localhost:8444/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg/providers/microsoft.redhatopenshift/openshiftclusters/cluster/kubeconfig/proxy\",\n                \"certificate-authority-data\": \"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\"\n            }\n        }\n    ],\n    \"users\": [\n        {\n            \"name\": \"user\",\n            \"user\": {\n                \"token\": \"password\"\n            }\n        }\n    ],\n    \"contexts\": [\n        {\n            \"name\": \"context\",\n            \"context\": {\n                \"cluster\": \"cluster\",\n                \"user\": \"user\",\n                \"namespace\": \"default\"\n            }\n        }\n    ],\n    \"current-context\": \"context\"\n}",
+			wantBody: "{\n    \"kind\": \"Config\",\n    \"apiVersion\": \"v1\",\n    \"preferences\": {},\n    \"clusters\": [\n        {\n            \"name\": \"cluster\",\n            \"cluster\": {\n                \"server\": \"https://localhost:8444/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg/providers/microsoft.redhatopenshift/openshiftclusters/cluster/kubeconfig/proxy\",\n                \"certificate-authority-data\": \"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\"\n            }\n        }\n    ],\n    \"users\": [\n        {\n            \"name\": \"user\",\n            \"user\": {\n                \"token\": \"03030303-0303-0303-0303-030303030001\"\n            }\n        }\n    ],\n    \"contexts\": [\n        {\n            \"name\": \"context\",\n            \"context\": {\n                \"cluster\": \"cluster\",\n                \"user\": \"user\",\n                \"namespace\": \"default\"\n            }\n        }\n    ],\n    \"current-context\": \"context\"\n}",
 		},
 		{
 			name:     "success - elevated",
@@ -83,7 +83,7 @@ func TestNew(t *testing.T) {
 			wantHeaders: http.Header{
 				"Content-Disposition": []string{`attachment; filename="cluster-elevated.kubeconfig"`},
 			},
-			wantBody: "{\n    \"kind\": \"Config\",\n    \"apiVersion\": \"v1\",\n    \"preferences\": {},\n    \"clusters\": [\n        {\n            \"name\": \"cluster\",\n            \"cluster\": {\n                \"server\": \"https://localhost:8444/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg/providers/microsoft.redhatopenshift/openshiftclusters/cluster/kubeconfig/proxy\",\n                \"certificate-authority-data\": \"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\"\n            }\n        }\n    ],\n    \"users\": [\n        {\n            \"name\": \"user\",\n            \"user\": {\n                \"token\": \"password\"\n            }\n        }\n    ],\n    \"contexts\": [\n        {\n            \"name\": \"context\",\n            \"context\": {\n                \"cluster\": \"cluster\",\n                \"user\": \"user\",\n                \"namespace\": \"default\"\n            }\n        }\n    ],\n    \"current-context\": \"context\"\n}",
+			wantBody: "{\n    \"kind\": \"Config\",\n    \"apiVersion\": \"v1\",\n    \"preferences\": {},\n    \"clusters\": [\n        {\n            \"name\": \"cluster\",\n            \"cluster\": {\n                \"server\": \"https://localhost:8444/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg/providers/microsoft.redhatopenshift/openshiftclusters/cluster/kubeconfig/proxy\",\n                \"certificate-authority-data\": \"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\"\n            }\n        }\n    ],\n    \"users\": [\n        {\n            \"name\": \"user\",\n            \"user\": {\n                \"token\": \"03030303-0303-0303-0303-030303030001\"\n            }\n        }\n    ],\n    \"contexts\": [\n        {\n            \"name\": \"context\",\n            \"context\": {\n                \"cluster\": \"cluster\",\n                \"user\": \"user\",\n                \"namespace\": \"default\"\n            }\n        }\n    ],\n    \"current-context\": \"context\"\n}",
 		},
 		{
 			name: "bad path",
@@ -148,9 +148,7 @@ func TestNew(t *testing.T) {
 			_, audit := testlog.NewAudit()
 			_, baseLog := testlog.New()
 			_, baseAccessLog := testlog.New()
-			k := New(baseLog, audit, _env, baseAccessLog, servingCert, elevatedGroupIDs, nil, dbPortal, nil, aadAuthenticatedRouter, &mux.Router{})
-
-			k.newToken = func() string { return password }
+			_ = New(baseLog, audit, _env, baseAccessLog, servingCert, elevatedGroupIDs, nil, dbPortal, nil, aadAuthenticatedRouter, &mux.Router{})
 
 			if tt.r != nil {
 				tt.r(r)
diff --git a/pkg/portal/kubeconfig/proxy_test.go b/pkg/portal/kubeconfig/proxy_test.go
index be2aa0184..77012c2a1 100644
--- a/pkg/portal/kubeconfig/proxy_test.go
+++ b/pkg/portal/kubeconfig/proxy_test.go
@@ -397,9 +397,7 @@ func TestProxy(t *testing.T) {
 			_, audit := testlog.NewAudit()
 			_, baseLog := testlog.New()
 			_, baseAccessLog := testlog.New()
-			k := New(baseLog, audit, _env, baseAccessLog, nil, nil, dbOpenShiftClusters, dbPortal, dialer, &mux.Router{}, unauthenticatedRouter)
-
-			k.newToken = func() string { return token }
+			_ = New(baseLog, audit, _env, baseAccessLog, nil, nil, dbOpenShiftClusters, dbPortal, dialer, &mux.Router{}, unauthenticatedRouter)
 
 			if tt.r != nil {
 				tt.r(r)
diff --git a/pkg/portal/middleware/aad_test.go b/pkg/portal/middleware/aad_test.go
index a759f2f61..6280db2c2 100644
--- a/pkg/portal/middleware/aad_test.go
+++ b/pkg/portal/middleware/aad_test.go
@@ -19,7 +19,6 @@ import (
 
 	"github.com/form3tech-oss/jwt-go"
 	"github.com/go-test/deep"
-	"github.com/gofrs/uuid"
 	"github.com/golang/mock/gomock"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/securecookie"
@@ -30,6 +29,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/oidc"
 	"github.com/Azure/ARO-RP/pkg/util/roundtripper"
 	utiltls "github.com/Azure/ARO-RP/pkg/util/tls"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	testlog "github.com/Azure/ARO-RP/test/util/log"
 )
 
@@ -490,7 +490,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "success",
 			request: func(a *aad) (*http.Request, error) {
-				uuid := uuid.Must(uuid.NewV4()).String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
@@ -540,8 +540,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "fail - state mismatch",
 			request: func(a *aad) (*http.Request, error) {
-				u, _ := uuid.NewV4()
-				uuid := u.String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
@@ -565,8 +564,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "fail - error returned",
 			request: func(a *aad) (*http.Request, error) {
-				u, _ := uuid.NewV4()
-				uuid := u.String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
@@ -592,8 +590,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "fail - oauther failed",
 			request: func(a *aad) (*http.Request, error) {
-				u, _ := uuid.NewV4()
-				uuid := u.String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
@@ -620,8 +617,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "fail - no idtoken",
 			request: func(a *aad) (*http.Request, error) {
-				u, _ := uuid.NewV4()
-				uuid := u.String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
@@ -646,8 +642,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "fail - verifier error",
 			request: func(a *aad) (*http.Request, error) {
-				u, _ := uuid.NewV4()
-				uuid := u.String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
@@ -677,8 +672,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "fail - invalid claims",
 			request: func(a *aad) (*http.Request, error) {
-				u, _ := uuid.NewV4()
-				uuid := u.String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
@@ -708,8 +702,7 @@ func TestCallback(t *testing.T) {
 		{
 			name: "fail - group mismatch",
 			request: func(a *aad) (*http.Request, error) {
-				u, _ := uuid.NewV4()
-				uuid := u.String()
+				uuid := uuid.DefaultGenerator.Generate()
 
 				cookie, err := securecookie.EncodeMulti(SessionName, map[interface{}]interface{}{
 					sessionKeyState: uuid,
diff --git a/pkg/portal/ssh/ssh_test.go b/pkg/portal/ssh/ssh_test.go
index 9d990bc3c..65087d2ae 100644
--- a/pkg/portal/ssh/ssh_test.go
+++ b/pkg/portal/ssh/ssh_test.go
@@ -29,7 +29,7 @@ func TestNew(t *testing.T) {
 	resourceID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg/providers/microsoft.redhatopenshift/openshiftclusters/cluster"
 	elevatedGroupIDs := []string{"10000000-0000-0000-0000-000000000000"}
 	username := "username"
-	password := "password"
+	password := "03030303-0303-0303-0303-030303030001"
 	master := 0
 
 	hostKey, _, err := utiltls.GenerateKeyAndCertificate("proxy", nil, nil, false, false)
@@ -60,7 +60,7 @@ func TestNew(t *testing.T) {
 				})
 			},
 			wantStatusCode: http.StatusOK,
-			wantBody:       "{\n    \"command\": \"ssh username@localhost\",\n    \"password\": \"password\"\n}",
+			wantBody:       "{\n    \"command\": \"ssh username@localhost\",\n    \"password\": \"03030303-0303-0303-0303-030303030001\"\n}",
 		},
 		{
 			name: "bad path",
@@ -140,13 +140,11 @@ func TestNew(t *testing.T) {
 
 			aadAuthenticatedRouter := &mux.Router{}
 
-			s, err := New(env, logrus.NewEntry(logrus.StandardLogger()), nil, nil, hostKey, elevatedGroupIDs, nil, dbPortal, nil, aadAuthenticatedRouter)
+			_, err = New(env, logrus.NewEntry(logrus.StandardLogger()), nil, nil, hostKey, elevatedGroupIDs, nil, dbPortal, nil, aadAuthenticatedRouter)
 			if err != nil {
 				t.Fatal(err)
 			}
 
-			s.newPassword = func() string { return password }
-
 			if tt.r != nil {
 				tt.r(r)
 			}
diff --git a/test/database/fixtures.go b/test/database/fixtures.go
index 814ccfb09..650f6f56e 100644
--- a/test/database/fixtures.go
+++ b/test/database/fixtures.go
@@ -6,8 +6,6 @@ package database
 import (
 	"context"
 
-	"github.com/gofrs/uuid"
-
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/database"
 )
@@ -151,7 +149,7 @@ func (f *Fixture) Create() error {
 
 	for _, i := range f.openshiftClusterDocuments {
 		if i.ID == "" {
-			i.ID = uuid.Must(uuid.NewV4()).String()
+			i.ID = f.openShiftClustersDatabase.NewUUID()
 		}
 		_, err := f.openShiftClustersDatabase.Create(ctx, i)
 		if err != nil {
diff --git a/test/database/inmemory.go b/test/database/inmemory.go
index 47a5f7361..a06e9c6c8 100644
--- a/test/database/inmemory.go
+++ b/test/database/inmemory.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/Azure/ARO-RP/pkg/database"
 	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+	"github.com/Azure/ARO-RP/test/util/deterministicuuid"
 )
 
 var jsonHandle *codec.JsonHandle
@@ -21,10 +22,11 @@ func init() {
 }
 
 func NewFakeOpenShiftClusters() (db database.OpenShiftClusters, client *cosmosdb.FakeOpenShiftClusterDocumentClient) {
+	uuid := deterministicuuid.NewTestUUIDGenerator(deterministicuuid.CLUSTERS)
 	coll := &fakeCollectionClient{}
 	client = cosmosdb.NewFakeOpenShiftClusterDocumentClient(jsonHandle)
 	injectOpenShiftClusters(client)
-	db = database.NewOpenShiftClustersWithProvidedClient(client, coll, "")
+	db = database.NewOpenShiftClustersWithProvidedClient(client, coll, "", uuid)
 	return db, client
 }
 
@@ -43,20 +45,23 @@ func NewFakeBilling() (db database.Billing, client *cosmosdb.FakeBillingDocument
 }
 
 func NewFakeAsyncOperations() (db database.AsyncOperations, client *cosmosdb.FakeAsyncOperationDocumentClient) {
+	uuid := deterministicuuid.NewTestUUIDGenerator(deterministicuuid.ASYNCOPERATIONS)
 	client = cosmosdb.NewFakeAsyncOperationDocumentClient(jsonHandle)
-	db = database.NewAsyncOperationsWithProvidedClient(client)
+	db = database.NewAsyncOperationsWithProvidedClient(client, uuid)
 	return db, client
 }
 
 func NewFakePortal() (db database.Portal, client *cosmosdb.FakePortalDocumentClient) {
+	uuid := deterministicuuid.NewTestUUIDGenerator(deterministicuuid.PORTAL)
 	client = cosmosdb.NewFakePortalDocumentClient(jsonHandle)
-	db = database.NewPortalWithProvidedClient(client)
+	db = database.NewPortalWithProvidedClient(client, uuid)
 	return db, client
 }
 
 func NewFakeGateway() (db database.Gateway, client *cosmosdb.FakeGatewayDocumentClient) {
+	uuid := deterministicuuid.NewTestUUIDGenerator(deterministicuuid.GATEWAY)
 	client = cosmosdb.NewFakeGatewayDocumentClient(jsonHandle)
-	db = database.NewGatewayWithProvidedClient(client)
+	db = database.NewGatewayWithProvidedClient(client, uuid)
 	return db, client
 }
 
diff --git a/test/util/deterministicuuid/deterministicuuid.go b/test/util/deterministicuuid/deterministicuuid.go
new file mode 100644
index 000000000..5de1bd4b1
--- /dev/null
+++ b/test/util/deterministicuuid/deterministicuuid.go
@@ -0,0 +1,48 @@
+package deterministicuuid
+
+import (
+	"bytes"
+
+	gofrsuuid "github.com/gofrs/uuid"
+
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
+)
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+const (
+	_ uint8 = iota
+	CLUSTERS
+	ASYNCOPERATIONS
+	PORTAL
+	GATEWAY
+)
+
+type gen struct {
+	namespace uint8
+	counter   uint16
+}
+
+// NewTestUUIDGenerator returns a uuid.Generator which generates UUIDv4s
+// suitable for testing.
+func NewTestUUIDGenerator(namespace uint8) uuid.Generator {
+	return &gen{
+		namespace: namespace,
+	}
+}
+
+// Generate generates a UUID that increments each call, using a counter to
+// specify the last two bytes and namespaced by the first byte.
+func (g *gen) Generate() string {
+	g.counter++
+
+	// repeat the namespace for the first 14 bytes to make an obvious non-random
+	// pattern
+	uuidBytes := bytes.Repeat([]byte{g.namespace}, 14)
+
+	// 16 bits of uuid ought to be enough for any test :)
+	uuidBytes = append(uuidBytes, byte(uint8(g.counter>>8)))
+	uuidBytes = append(uuidBytes, byte(uint8(g.counter)))
+	return gofrsuuid.FromBytesOrNil(uuidBytes).String()
+}
diff --git a/test/util/deterministicuuid/deterministicuuid_test.go b/test/util/deterministicuuid/deterministicuuid_test.go
new file mode 100644
index 000000000..61dc7ec66
--- /dev/null
+++ b/test/util/deterministicuuid/deterministicuuid_test.go
@@ -0,0 +1,51 @@
+package deterministicuuid
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"testing"
+)
+
+func TestDeterministicUUID(t *testing.T) {
+	g := &gen{}
+
+	// generate until it's obvious it's base-16 :)
+	uuids := []string{
+		"00000000-0000-0000-0000-000000000001",
+		"00000000-0000-0000-0000-000000000002",
+		"00000000-0000-0000-0000-000000000003",
+		"00000000-0000-0000-0000-000000000004",
+		"00000000-0000-0000-0000-000000000005",
+		"00000000-0000-0000-0000-000000000006",
+		"00000000-0000-0000-0000-000000000007",
+		"00000000-0000-0000-0000-000000000008",
+		"00000000-0000-0000-0000-000000000009",
+		"00000000-0000-0000-0000-00000000000a",
+		"00000000-0000-0000-0000-00000000000b",
+		"00000000-0000-0000-0000-00000000000c",
+		"00000000-0000-0000-0000-00000000000d",
+		"00000000-0000-0000-0000-00000000000e",
+		"00000000-0000-0000-0000-00000000000f",
+		"00000000-0000-0000-0000-000000000010",
+	}
+
+	for _, u := range uuids {
+		genned := g.Generate()
+		if genned != u {
+			t.Error(u, genned)
+		}
+	}
+
+	g.counter = 256
+	genned := g.Generate()
+	if genned != "00000000-0000-0000-0000-000000000101" {
+		t.Errorf("not bitshifted correctly: %s", genned)
+	}
+
+	namespaced := &gen{namespace: 12}
+	genned = namespaced.Generate()
+	if genned != "0c0c0c0c-0c0c-0c0c-0c0c-0c0c0c0c0001" {
+		t.Errorf("not namespaced correctly: %s", genned)
+	}
+}