Repository: AthenZ/athenz

Issue:
Extend GCP Provider to remove equivalency check between GCP and Athenz service account names.
the current design requires that the service account name used to launch an instance must match to the athenz service name in the associated domain. However, gcp also has a 6 limit min requirement for service name. So if we have a service, for example, called zts and we want to deploy it in gcp, then we can never get an identity since we can't create a service account name called zts.

We should provide the capability in Athenz to provide an identity based on the authorization check if the original equivalency check with the service account name is not satisified.

For example, we create a service account called zts-gcp in a project with project id: gcp-athenz. The token included in the credentials is assumed to be in the format {gcp-project-id}.{service-account-name} - with our example gcp-athenz.zts-gcp. Now, we want this instance to have the athenz identity athenz.prod.zts where athenz.prod is the domain mapped with gcp-project gcp-athenz and zts is the service name.

So before rejecting the request because zts-gcp does not match with zts service name, the provider will carry out the following authorization check:

Principal: athenz.prod.zts
Action: gcp.assume_identity
Resource: athenz.prod:services/zts-gcp

If this authorization check passes, then the zts service identity is authorized to assume the zts-gcp service account and as such the certificate will be issued.

Discussion:

Solution patch:
diff --git a/clients/go/msd/client.go b/clients/go/msd/client.go
index 2ddc989b8..ef32b9175 100644
--- a/clients/go/msd/client.go
+++ b/clients/go/msd/client.go
@@ -292,6 +292,42 @@ func encodeParams(objs ...string) string {
 	return "?" + s[1:]
 }
 
+func (client MSDClient) PostAthenzDependencyRequest(request *AthenzDependencyRequest) (*AthenzDependencyResponse, error) {
+	var data *AthenzDependencyResponse
+	url := client.URL + "/domains/dependency-check"
+	contentBytes, err := json.Marshal(request)
+	if err != nil {
+		return data, err
+	}
+	resp, err := client.httpPost(url, nil, contentBytes)
+	if err != nil {
+		return data, err
+	}
+	defer resp.Body.Close()
+	switch resp.StatusCode {
+	case 200:
+		err = json.NewDecoder(resp.Body).Decode(&data)
+		if err != nil {
+			return data, err
+		}
+		return data, nil
+	default:
+		var errobj rdl.ResourceError
+		contentBytes, err = io.ReadAll(resp.Body)
+		if err != nil {
+			return data, err
+		}
+		json.Unmarshal(contentBytes, &errobj)
+		if errobj.Code == 0 {
+			errobj.Code = resp.StatusCode
+		}
+		if errobj.Message == "" {
+			errobj.Message = string(contentBytes)
+		}
+		return data, errobj
+	}
+}
+
 func (client MSDClient) GetTransportPolicyRules(matchingTag string) (*TransportPolicyRules, string, error) {
 	var data *TransportPolicyRules
 	headers := map[string]string{
diff --git a/clients/go/msd/model.go b/clients/go/msd/model.go
index c39206ed1..e1e596673 100644
--- a/clients/go/msd/model.go
+++ b/clients/go/msd/model.go
@@ -12,6 +12,375 @@ var _ = rdl.Version
 var _ = json.Marshal
 var _ = fmt.Printf
 
+// AthenzEntityAction - Copyright The Athenz Authors Licensed under the terms
+// of the Apache version 2.0 license. See LICENSE file for terms.
+type AthenzEntityAction int
+
+// AthenzEntityAction constants
+const (
+	_ AthenzEntityAction = iota
+	create
+	delete
+	update
+)
+
+var namesAthenzEntityAction = []string{
+	create: "create",
+	delete: "delete",
+	update: "update",
+}
+
+// NewAthenzEntityAction - return a string representation of the enum
+func NewAthenzEntityAction(init ...interface{}) AthenzEntityAction {
+	if len(init) == 1 {
+		switch v := init[0].(type) {
+		case AthenzEntityAction:
+			return v
+		case int:
+			return AthenzEntityAction(v)
+		case int32:
+			return AthenzEntityAction(v)
+		case string:
+			for i, s := range namesAthenzEntityAction {
+				if s == v {
+					return AthenzEntityAction(i)
+				}
+			}
+		default:
+			panic("Bad init value for AthenzEntityAction enum")
+		}
+	}
+	return AthenzEntityAction(0) //default to the first enum value
+}
+
+// String - return a string representation of the enum
+func (e AthenzEntityAction) String() string {
+	return namesAthenzEntityAction[e]
+}
+
+// SymbolSet - return an array of all valid string representations (symbols) of the enum
+func (e AthenzEntityAction) SymbolSet() []string {
+	return namesAthenzEntityAction
+}
+
+// MarshalJSON is defined for proper JSON encoding of a AthenzEntityAction
+func (e AthenzEntityAction) MarshalJSON() ([]byte, error) {
+	return json.Marshal(e.String())
+}
+
+// UnmarshalJSON is defined for proper JSON decoding of a AthenzEntityAction
+func (e *AthenzEntityAction) UnmarshalJSON(b []byte) error {
+	var j string
+	err := json.Unmarshal(b, &j)
+	if err == nil {
+		s := string(j)
+		for v, s2 := range namesAthenzEntityAction {
+			if s == s2 {
+				*e = AthenzEntityAction(v)
+				return nil
+			}
+		}
+		err = fmt.Errorf("Bad enum symbol for type AthenzEntityAction: %s", s)
+	}
+	return err
+}
+
+// AthenzEntityType -
+type AthenzEntityType int
+
+// AthenzEntityType constants
+const (
+	_ AthenzEntityType = iota
+	domain
+	role
+	group
+	service
+	policy
+	entity
+)
+
+var namesAthenzEntityType = []string{
+	domain:  "domain",
+	role:    "role",
+	group:   "group",
+	service: "service",
+	policy:  "policy",
+	entity:  "entity",
+}
+
+// NewAthenzEntityType - return a string representation of the enum
+func NewAthenzEntityType(init ...interface{}) AthenzEntityType {
+	if len(init) == 1 {
+		switch v := init[0].(type) {
+		case AthenzEntityType:
+			return v
+		case int:
+			return AthenzEntityType(v)
+		case int32:
+			return AthenzEntityType(v)
+		case string:
+			for i, s := range namesAthenzEntityType {
+				if s == v {
+					return AthenzEntityType(i)
+				}
+			}
+		default:
+			panic("Bad init value for AthenzEntityType enum")
+		}
+	}
+	return AthenzEntityType(0) //default to the first enum value
+}
+
+// String - return a string representation of the enum
+func (e AthenzEntityType) String() string {
+	return namesAthenzEntityType[e]
+}
+
+// SymbolSet - return an array of all valid string representations (symbols) of the enum
+func (e AthenzEntityType) SymbolSet() []string {
+	return namesAthenzEntityType
+}
+
+// MarshalJSON is defined for proper JSON encoding of a AthenzEntityType
+func (e AthenzEntityType) MarshalJSON() ([]byte, error) {
+	return json.Marshal(e.String())
+}
+
+// UnmarshalJSON is defined for proper JSON decoding of a AthenzEntityType
+func (e *AthenzEntityType) UnmarshalJSON(b []byte) error {
+	var j string
+	err := json.Unmarshal(b, &j)
+	if err == nil {
+		s := string(j)
+		for v, s2 := range namesAthenzEntityType {
+			if s == s2 {
+				*e = AthenzEntityType(v)
+				return nil
+			}
+		}
+		err = fmt.Errorf("Bad enum symbol for type AthenzEntityType: %s", s)
+	}
+	return err
+}
+
+// AthenzDependencyResponseStatus -
+type AthenzDependencyResponseStatus int
+
+// AthenzDependencyResponseStatus constants
+const (
+	_ AthenzDependencyResponseStatus = iota
+	allow
+	deny
+)
+
+var namesAthenzDependencyResponseStatus = []string{
+	allow: "allow",
+	deny:  "deny",
+}
+
+// NewAthenzDependencyResponseStatus - return a string representation of the enum
+func NewAthenzDependencyResponseStatus(init ...interface{}) AthenzDependencyResponseStatus {
+	if len(init) == 1 {
+		switch v := init[0].(type) {
+		case AthenzDependencyResponseStatus:
+			return v
+		case int:
+			return AthenzDependencyResponseStatus(v)
+		case int32:
+			return AthenzDependencyResponseStatus(v)
+		case string:
+			for i, s := range namesAthenzDependencyResponseStatus {
+				if s == v {
+					return AthenzDependencyResponseStatus(i)
+				}
+			}
+		default:
+			panic("Bad init value for AthenzDependencyResponseStatus enum")
+		}
+	}
+	return AthenzDependencyResponseStatus(0) //default to the first enum value
+}
+
+// String - return a string representation of the enum
+func (e AthenzDependencyResponseStatus) String() string {
+	return namesAthenzDependencyResponseStatus[e]
+}
+
+// SymbolSet - return an array of all valid string representations (symbols) of the enum
+func (e AthenzDependencyResponseStatus) SymbolSet() []string {
+	return namesAthenzDependencyResponseStatus
+}
+
+// MarshalJSON is defined for proper JSON encoding of a AthenzDependencyResponseStatus
+func (e AthenzDependencyResponseStatus) MarshalJSON() ([]byte, error) {
+	return json.Marshal(e.String())
+}
+
+// UnmarshalJSON is defined for proper JSON decoding of a AthenzDependencyResponseStatus
+func (e *AthenzDependencyResponseStatus) UnmarshalJSON(b []byte) error {
+	var j string
+	err := json.Unmarshal(b, &j)
+	if err == nil {
+		s := string(j)
+		for v, s2 := range namesAthenzDependencyResponseStatus {
+			if s == s2 {
+				*e = AthenzDependencyResponseStatus(v)
+				return nil
+			}
+		}
+		err = fmt.Errorf("Bad enum symbol for type AthenzDependencyResponseStatus: %s", s)
+	}
+	return err
+}
+
+// AthenzDependencyRequest -
+type AthenzDependencyRequest struct {
+
+	//
+	// domain action like, create/delete/...
+	//
+	Operation AthenzEntityAction `json:"operation"`
+
+	//
+	// domain name
+	//
+	DomainName string `json:"domainName"`
+
+	//
+	// entity type like, domain/role/...
+	//
+	ObjectType AthenzEntityType `json:"objectType"`
+
+	//
+	// name of the athenz entity
+	//
+	ObjectName string `json:"objectName"`
+
+	//
+	// principal of the executing operation
+	//
+	Principal string `json:"principal"`
+
+	//
+	// provider service identity
+	//
+	Provider string `json:"provider"`
+}
+
+// NewAthenzDependencyRequest - creates an initialized AthenzDependencyRequest instance, returns a pointer to it
+func NewAthenzDependencyRequest(init ...*AthenzDependencyRequest) *AthenzDependencyRequest {
+	var o *AthenzDependencyRequest
+	if len(init) == 1 {
+		o = init[0]
+	} else {
+		o = new(AthenzDependencyRequest)
+	}
+	return o
+}
+
+type rawAthenzDependencyRequest AthenzDependencyRequest
+
+// UnmarshalJSON is defined for proper JSON decoding of a AthenzDependencyRequest
+func (self *AthenzDependencyRequest) UnmarshalJSON(b []byte) error {
+	var m rawAthenzDependencyRequest
+	err := json.Unmarshal(b, &m)
+	if err == nil {
+		o := AthenzDependencyRequest(m)
+		*self = o
+		err = self.Validate()
+	}
+	return err
+}
+
+// Validate - checks for missing required fields, etc
+func (self *AthenzDependencyRequest) Validate() error {
+	if self.DomainName == "" {
+		return fmt.Errorf("AthenzDependencyRequest.domainName is missing but is a required field")
+	} else {
+		val := rdl.Validate(MSDSchema(), "String", self.DomainName)
+		if !val.Valid {
+			return fmt.Errorf("AthenzDependencyRequest.domainName does not contain a valid String (%v)", val.Error)
+		}
+	}
+	if self.ObjectName == "" {
+		return fmt.Errorf("AthenzDependencyRequest.objectName is missing but is a required field")
+	} else {
+		val := rdl.Validate(MSDSchema(), "String", self.ObjectName)
+		if !val.Valid {
+			return fmt.Errorf("AthenzDependencyRequest.objectName does not contain a valid String (%v)", val.Error)
+		}
+	}
+	if self.Principal == "" {
+		return fmt.Errorf("AthenzDependencyRequest.principal is missing but is a required field")
+	} else {
+		val := rdl.Validate(MSDSchema(), "String", self.Principal)
+		if !val.Valid {
+			return fmt.Errorf("AthenzDependencyRequest.principal does not contain a valid String (%v)", val.Error)
+		}
+	}
+	if self.Provider == "" {
+		return fmt.Errorf("AthenzDependencyRequest.provider is missing but is a required field")
+	} else {
+		val := rdl.Validate(MSDSchema(), "String", self.Provider)
+		if !val.Valid {
+			return fmt.Errorf("AthenzDependencyRequest.provider does not contain a valid String (%v)", val.Error)
+		}
+	}
+	return nil
+}
+
+// AthenzDependencyResponse -
+type AthenzDependencyResponse struct {
+
+	//
+	// whether the request is allowed or denied
+	//
+	Status AthenzDependencyResponseStatus `json:"status"`
+
+	//
+	// reason for denial
+	//
+	Message string `json:"message"`
+}
+
+// NewAthenzDependencyResponse - creates an initialized AthenzDependencyResponse instance, returns a pointer to it
+func NewAthenzDependencyResponse(init ...*AthenzDependencyResponse) *AthenzDependencyResponse {
+	var o *AthenzDependencyResponse
+	if len(init) == 1 {
+		o = init[0]
+	} else {
+		o = new(AthenzDependencyResponse)
+	}
+	return o
+}
+
+type rawAthenzDependencyResponse AthenzDependencyResponse
+
+// UnmarshalJSON is defined for proper JSON decoding of a AthenzDependencyResponse
+func (self *AthenzDependencyResponse) UnmarshalJSON(b []byte) error {
+	var m rawAthenzDependencyResponse
+	err := json.Unmarshal(b, &m)
+	if err == nil {
+		o := AthenzDependencyResponse(m)
+		*self = o
+		err = self.Validate()
+	}
+	return err
+}
+
+// Validate - checks for missing required fields, etc
+func (self *AthenzDependencyResponse) Validate() error {
+	if self.Message == "" {
+		return fmt.Errorf("AthenzDependencyResponse.message is missing but is a required field")
+	} else {
+		val := rdl.Validate(MSDSchema(), "String", self.Message)
+		if !val.Valid {
+			return fmt.Errorf("AthenzDependencyResponse.message does not contain a valid String (%v)", val.Error)
+		}
+	}
+	return nil
+}
+
 // SimpleName - Copyright The Athenz Authors Licensed under the terms of the
 // Apache version 2.0 license. See LICENSE file for terms. Common name types
 // used by several API definitions A simple identifier, an element of compound
diff --git a/clients/go/msd/msd_schema.go b/clients/go/msd/msd_schema.go
index d59c545c4..ea27f007b 100644
--- a/clients/go/msd/msd_schema.go
+++ b/clients/go/msd/msd_schema.go
@@ -16,6 +16,41 @@ func init() {
 	sb.Namespace("com.yahoo.athenz.msd")
 	sb.Comment("Copyright The Athenz Authors Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. The Micro Segmentation Defense (MSD) API")
 
+	tAthenzEntityAction := rdl.NewEnumTypeBuilder("Enum", "AthenzEntityAction")
+	tAthenzEntityAction.Comment("Copyright The Athenz Authors Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.")
+	tAthenzEntityAction.Element("create", "")
+	tAthenzEntityAction.Element("delete", "")
+	tAthenzEntityAction.Element("update", "")
+	sb.AddType(tAthenzEntityAction.Build())
+
+	tAthenzEntityType := rdl.NewEnumTypeBuilder("Enum", "AthenzEntityType")
+	tAthenzEntityType.Element("domain", "")
+	tAthenzEntityType.Element("role", "")
+	tAthenzEntityType.Element("group", "")
+	tAthenzEntityType.Element("service", "")
+	tAthenzEntityType.Element("policy", "")
+	tAthenzEntityType.Element("entity", "")
+	sb.AddType(tAthenzEntityType.Build())
+
+	tAthenzDependencyResponseStatus := rdl.NewEnumTypeBuilder("Enum", "AthenzDependencyResponseStatus")
+	tAthenzDependencyResponseStatus.Element("allow", "")
+	tAthenzDependencyResponseStatus.Element("deny", "")
+	sb.AddType(tAthenzDependencyResponseStatus.Build())
+
+	tAthenzDependencyRequest := rdl.NewStructTypeBuilder("Struct", "AthenzDependencyRequest")
+	tAthenzDependencyRequest.Field("operation", "AthenzEntityAction", false, nil, "domain action like, create/delete/...")
+	tAthenzDependencyRequest.Field("domainName", "String", false, nil, "domain name")
+	tAthenzDependencyRequest.Field("objectType", "AthenzEntityType", false, nil, "entity type like, domain/role/...")
+	tAthenzDependencyRequest.Field("objectName", "String", false, nil, "name of the athenz entity")
+	tAthenzDependencyRequest.Field("principal", "String", true, nil, "principal of the executing operation")
+	tAthenzDependencyRequest.Field("provider", "String", true, nil, "provider service identity")
+	sb.AddType(tAthenzDependencyRequest.Build())
+
+	tAthenzDependencyResponse := rdl.NewStructTypeBuilder("Struct", "AthenzDependencyResponse")
+	tAthenzDependencyResponse.Field("status", "AthenzDependencyResponseStatus", false, nil, "whether the request is allowed or denied")
+	tAthenzDependencyResponse.Field("message", "String", true, nil, "reason for denial")
+	sb.AddType(tAthenzDependencyResponse.Build())
+
 	tSimpleName := rdl.NewStringTypeBuilder("SimpleName")
 	tSimpleName.Comment("Copyright The Athenz Authors Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. Common name types used by several API definitions A simple identifier, an element of compound name.")
 	tSimpleName.Pattern("[a-zA-Z0-9_][a-zA-Z0-9_-]*")
@@ -677,6 +712,16 @@ func init() {
 	tRdl_Schema.MapField("annotations", "rdl.ExtendedAnnotation", "String", true, "additional annotations starting with \"x_\"")
 	sb.AddType(tRdl_Schema.Build())
 
+	mPostAthenzDependencyRequest := rdl.NewResourceBuilder("AthenzDependencyResponse", "POST", "/domains/dependency-check")
+	mPostAthenzDependencyRequest.Comment("Athenz entity Dependency API webhook")
+	mPostAthenzDependencyRequest.Input("request", "AthenzDependencyRequest", false, "", "", false, nil, "request")
+	mPostAthenzDependencyRequest.Auth("", "", true, "")
+	mPostAthenzDependencyRequest.Exception("BAD_REQUEST", "ResourceError", "")
+	mPostAthenzDependencyRequest.Exception("INTERNAL_SERVER_ERROR", "ResourceError", "")
+	mPostAthenzDependencyRequest.Exception("NOT_FOUND", "ResourceError", "")
+	mPostAthenzDependencyRequest.Exception("UNAUTHORIZED", "ResourceError", "")
+	sb.AddResource(mPostAthenzDependencyRequest.Build())
+
 	mGetTransportPolicyRules := rdl.NewResourceBuilder("TransportPolicyRules", "GET", "/transportpolicies")
 	mGetTransportPolicyRules.Comment("API endpoint to get the transport policy rules defined in Athenz")
 	mGetTransportPolicyRules.Input("matchingTag", "String", false, "", "If-None-Match", false, nil, "Retrieved from the previous request, this timestamp specifies to the server to return any policies modified since this time")
diff --git a/clients/go/zms/model.go b/clients/go/zms/model.go
index 0bc75cac1..a242b5eac 100644
--- a/clients/go/zms/model.go
+++ b/clients/go/zms/model.go
@@ -6468,27 +6468,27 @@ type AuthHistory struct {
 	//
 	// Name of the domain from URI
 	//
-	UriDomain DomainName `json:"uriDomain"`
+	UriDomain DomainName `json:"uriDomain,omitempty" rdl:"optional" yaml:",omitempty"`
 
 	//
 	// Principal domain
 	//
-	PrincipalDomain DomainName `json:"principalDomain"`
+	PrincipalDomain DomainName `json:"principalDomain,omitempty" rdl:"optional" yaml:",omitempty"`
 
 	//
 	// Principal name
 	//
-	PrincipalName SimpleName `json:"principalName"`
+	PrincipalName SimpleName `json:"principalName,omitempty" rdl:"optional" yaml:",omitempty"`
 
 	//
 	// Last authorization event timestamp
 	//
-	Timestamp rdl.Timestamp `json:"timestamp"`
+	Timestamp *rdl.Timestamp `json:"timestamp,omitempty" rdl:"optional" yaml:",omitempty"`
 
 	//
 	// Last authorization endpoint used
 	//
-	Endpoint string `json:"endpoint"`
+	Endpoint string `json:"endpoint" rdl:"optional" yaml:",omitempty"`
 
 	//
 	// Time until the record will expire
@@ -6523,36 +6523,25 @@ func (self *AuthHistory) UnmarshalJSON(b []byte) error {
 
 // Validate - checks for missing required fields, etc
 func (self *AuthHistory) Validate() error {
-	if self.UriDomain == "" {
-		return fmt.Errorf("AuthHistory.uriDomain is missing but is a required field")
-	} else {
+	if self.UriDomain != "" {
 		val := rdl.Validate(ZMSSchema(), "DomainName", self.UriDomain)
 		if !val.Valid {
 			return fmt.Errorf("AuthHistory.uriDomain does not contain a valid DomainName (%v)", val.Error)
 		}
 	}
-	if self.PrincipalDomain == "" {
-		return fmt.Errorf("AuthHistory.principalDomain is missing but is a required field")
-	} else {
+	if self.PrincipalDomain != "" {
 		val := rdl.Validate(ZMSSchema(), "DomainName", self.PrincipalDomain)
 		if !val.Valid {
 			return fmt.Errorf("AuthHistory.principalDomain does not contain a valid DomainName (%v)", val.Error)
 		}
 	}
-	if self.PrincipalName == "" {
-		return fmt.Errorf("AuthHistory.principalName is missing but is a required field")
-	} else {
+	if self.PrincipalName != "" {
 		val := rdl.Validate(ZMSSchema(), "SimpleName", self.PrincipalName)
 		if !val.Valid {
 			return fmt.Errorf("AuthHistory.principalName does not contain a valid SimpleName (%v)", val.Error)
 		}
 	}
-	if self.Timestamp.IsZero() {
-		return fmt.Errorf("AuthHistory: Missing required field: timestamp")
-	}
-	if self.Endpoint == "" {
-		return fmt.Errorf("AuthHistory.endpoint is missing but is a required field")
-	} else {
+	if self.Endpoint != "" {
 		val := rdl.Validate(ZMSSchema(), "String", self.Endpoint)
 		if !val.Valid {
 			return fmt.Errorf("AuthHistory.endpoint does not contain a valid String (%v)", val.Error)
diff --git a/clients/go/zms/zms_schema.go b/clients/go/zms/zms_schema.go
index 69b05ad2e..bb1ed57f5 100644
--- a/clients/go/zms/zms_schema.go
+++ b/clients/go/zms/zms_schema.go
@@ -650,11 +650,11 @@ func init() {
 	sb.AddType(tDomainMetaStoreValidValuesList.Build())
 
 	tAuthHistory := rdl.NewStructTypeBuilder("Struct", "AuthHistory")
-	tAuthHistory.Field("uriDomain", "DomainName", false, nil, "Name of the domain from URI")
-	tAuthHistory.Field("principalDomain", "DomainName", false, nil, "Principal domain")
-	tAuthHistory.Field("principalName", "SimpleName", false, nil, "Principal name")
-	tAuthHistory.Field("timestamp", "Timestamp", false, nil, "Last authorization event timestamp")
-	tAuthHistory.Field("endpoint", "String", false, nil, "Last authorization endpoint used")
+	tAuthHistory.Field("uriDomain", "DomainName", true, nil, "Name of the domain from URI")
+	tAuthHistory.Field("principalDomain", "DomainName", true, nil, "Principal domain")
+	tAuthHistory.Field("principalName", "SimpleName", true, nil, "Principal name")
+	tAuthHistory.Field("timestamp", "Timestamp", true, nil, "Last authorization event timestamp")
+	tAuthHistory.Field("endpoint", "String", true, nil, "Last authorization endpoint used")
 	tAuthHistory.Field("ttl", "Int64", false, nil, "Time until the record will expire")
 	sb.AddType(tAuthHistory.Build())
 
diff --git a/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDClient.java b/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDClient.java
index 42472187d..e012ff86e 100644
--- a/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDClient.java
+++ b/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDClient.java
@@ -279,6 +279,29 @@ public class MSDClient implements Closeable {
         }
     }
 
+    /**
+     * Api to perform a a dependency check against MSD, for teh given athenz service
+     *
+     * @param domain  name of the domain
+     * @param service name of the service
+     * @param action athenz entity action
+     */
+    public AthenzDependencyResponse serviceDependencyCheck(String domain, String service, AthenzEntityAction action) {
+        AthenzDependencyRequest request = new AthenzDependencyRequest();
+        request.setDomainName(domain);
+        request.setOperation(action);
+        request.setObjectType(AthenzEntityType.service);
+        request.setObjectName(service);
+
+        try {
+            return client.postAthenzDependencyRequest(request);
+        } catch (ClientResourceException ex) {
+            throw new MSDClientException(ex.getCode(), ex.getData());
+        } catch (Exception ex) {
+            throw new MSDClientException(ClientResourceException.BAD_REQUEST, ex.getMessage());
+        }
+    }
+
     /**
      * Close the MSDClient object and release any allocated resources.
      */
diff --git a/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDRDLGeneratedClient.java b/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDRDLGeneratedClient.java
index e7a316ed3..78da48cb0 100644
--- a/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDRDLGeneratedClient.java
+++ b/clients/java/msd/src/main/java/com/yahoo/athenz/msd/MSDRDLGeneratedClient.java
@@ -157,6 +157,35 @@ public class MSDRDLGeneratedClient {
         }
     }
 
+    public AthenzDependencyResponse postAthenzDependencyRequest(AthenzDependencyRequest request) throws URISyntaxException, IOException {
+        UriTemplateBuilder uriTemplateBuilder = new UriTemplateBuilder(baseUrl, "/domains/dependency-check");
+        URIBuilder uriBuilder = new URIBuilder(uriTemplateBuilder.getUri());
+        HttpEntity httpEntity = new StringEntity(jsonMapper.writeValueAsString(request), ContentType.APPLICATION_JSON);
+        ClassicHttpRequest httpUriRequest = ClassicRequestBuilder.post()
+            .setUri(uriBuilder.build())
+            .setEntity(httpEntity)
+            .build();
+        if (credsHeader != null) {
+            httpUriRequest.addHeader(credsHeader, credsToken);
+        }
+        HttpEntity httpResponseEntity = null;
+        try (CloseableHttpResponse httpResponse = client.execute(httpUriRequest, httpContext)) {
+            int code = httpResponse.getCode();
+            httpResponseEntity = httpResponse.getEntity();
+            switch (code) {
+            case 200:
+                return jsonMapper.readValue(httpResponseEntity.getContent(), AthenzDependencyResponse.class);
+            default:
+                final String errorData = (httpResponseEntity == null) ? null : getStringResponseEntity(httpResponseEntity);
+                throw (errorData != null && !errorData.isEmpty())
+                    ? new ClientResourceException(code, jsonMapper.readValue(errorData, ClientResourceError.class))
+                    : new ClientResourceException(code);
+            }
+        } finally {
+            EntityUtils.consumeQuietly(httpResponseEntity);
+        }
+    }
+
     public TransportPolicyRules getTransportPolicyRules(String matchingTag, java.util.Map<String, java.util.List<String>> headers) throws URISyntaxException, IOException {
         UriTemplateBuilder uriTemplateBuilder = new UriTemplateBuilder(baseUrl, "/transportpolicies");
         URIBuilder uriBuilder = new URIBuilder(uriTemplateBuilder.getUri());
diff --git a/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyRequest.java b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyRequest.java
new file mode 100644
index 000000000..1a74743d9
--- /dev/null
+++ b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyRequest.java
@@ -0,0 +1,97 @@
+//
+// This file generated by rdl 1.5.2. Do not modify!
+//
+
+package com.yahoo.athenz.msd;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.yahoo.rdl.*;
+
+//
+// AthenzDependencyRequest -
+//
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AthenzDependencyRequest {
+    public AthenzEntityAction operation;
+    public String domainName;
+    public AthenzEntityType objectType;
+    public String objectName;
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    public String principal;
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    public String provider;
+
+    public AthenzDependencyRequest setOperation(AthenzEntityAction operation) {
+        this.operation = operation;
+        return this;
+    }
+    public AthenzEntityAction getOperation() {
+        return operation;
+    }
+    public AthenzDependencyRequest setDomainName(String domainName) {
+        this.domainName = domainName;
+        return this;
+    }
+    public String getDomainName() {
+        return domainName;
+    }
+    public AthenzDependencyRequest setObjectType(AthenzEntityType objectType) {
+        this.objectType = objectType;
+        return this;
+    }
+    public AthenzEntityType getObjectType() {
+        return objectType;
+    }
+    public AthenzDependencyRequest setObjectName(String objectName) {
+        this.objectName = objectName;
+        return this;
+    }
+    public String getObjectName() {
+        return objectName;
+    }
+    public AthenzDependencyRequest setPrincipal(String principal) {
+        this.principal = principal;
+        return this;
+    }
+    public String getPrincipal() {
+        return principal;
+    }
+    public AthenzDependencyRequest setProvider(String provider) {
+        this.provider = provider;
+        return this;
+    }
+    public String getProvider() {
+        return provider;
+    }
+
+    @Override
+    public boolean equals(Object another) {
+        if (this != another) {
+            if (another == null || another.getClass() != AthenzDependencyRequest.class) {
+                return false;
+            }
+            AthenzDependencyRequest a = (AthenzDependencyRequest) another;
+            if (operation == null ? a.operation != null : !operation.equals(a.operation)) {
+                return false;
+            }
+            if (domainName == null ? a.domainName != null : !domainName.equals(a.domainName)) {
+                return false;
+            }
+            if (objectType == null ? a.objectType != null : !objectType.equals(a.objectType)) {
+                return false;
+            }
+            if (objectName == null ? a.objectName != null : !objectName.equals(a.objectName)) {
+                return false;
+            }
+            if (principal == null ? a.principal != null : !principal.equals(a.principal)) {
+                return false;
+            }
+            if (provider == null ? a.provider != null : !provider.equals(a.provider)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyResponse.java b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyResponse.java
new file mode 100644
index 000000000..f86839f32
--- /dev/null
+++ b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyResponse.java
@@ -0,0 +1,51 @@
+//
+// This file generated by rdl 1.5.2. Do not modify!
+//
+
+package com.yahoo.athenz.msd;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.yahoo.rdl.*;
+
+//
+// AthenzDependencyResponse -
+//
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AthenzDependencyResponse {
+    public AthenzDependencyResponseStatus status;
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    public String message;
+
+    public AthenzDependencyResponse setStatus(AthenzDependencyResponseStatus status) {
+        this.status = status;
+        return this;
+    }
+    public AthenzDependencyResponseStatus getStatus() {
+        return status;
+    }
+    public AthenzDependencyResponse setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+    public String getMessage() {
+        return message;
+    }
+
+    @Override
+    public boolean equals(Object another) {
+        if (this != another) {
+            if (another == null || another.getClass() != AthenzDependencyResponse.class) {
+                return false;
+            }
+            AthenzDependencyResponse a = (AthenzDependencyResponse) another;
+            if (status == null ? a.status != null : !status.equals(a.status)) {
+                return false;
+            }
+            if (message == null ? a.message != null : !message.equals(a.message)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyResponseStatus.java b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyResponseStatus.java
new file mode 100644
index 000000000..f5e721df1
--- /dev/null
+++ b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzDependencyResponseStatus.java
@@ -0,0 +1,24 @@
+//
+// This file generated by rdl 1.5.2. Do not modify!
+//
+
+package com.yahoo.athenz.msd;
+import com.yahoo.rdl.*;
+
+//
+// AthenzDependencyResponseStatus -
+//
+public enum AthenzDependencyResponseStatus {
+    allow,
+    deny;
+
+    public static AthenzDependencyResponseStatus fromString(String v) {
+        for (AthenzDependencyResponseStatus e : values()) {
+            if (e.toString().equals(v)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("Invalid string representation for AthenzDependencyResponseStatus: " + v);
+    }
+}
+
diff --git a/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzEntityAction.java b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzEntityAction.java
new file mode 100644
index 000000000..4880edfd6
--- /dev/null
+++ b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzEntityAction.java
@@ -0,0 +1,26 @@
+//
+// This file generated by rdl 1.5.2. Do not modify!
+//
+
+package com.yahoo.athenz.msd;
+import com.yahoo.rdl.*;
+
+//
+// AthenzEntityAction - Copyright The Athenz Authors Licensed under the terms
+// of the Apache version 2.0 license. See LICENSE file for terms.
+//
+public enum AthenzEntityAction {
+    create,
+    delete,
+    update;
+
+    public static AthenzEntityAction fromString(String v) {
+        for (AthenzEntityAction e : values()) {
+            if (e.toString().equals(v)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("Invalid string representation for AthenzEntityAction: " + v);
+    }
+}
+
diff --git a/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzEntityType.java b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzEntityType.java
new file mode 100644
index 000000000..5fa37f922
--- /dev/null
+++ b/core/msd/src/main/java/com/yahoo/athenz/msd/AthenzEntityType.java
@@ -0,0 +1,28 @@
+//
+// This file generated by rdl 1.5.2. Do not modify!
+//
+
+package com.yahoo.athenz.msd;
+import com.yahoo.rdl.*;
+
+//
+// AthenzEntityType -
+//
+public enum AthenzEntityType {
+    domain,
+    role,
+    group,
+    service,
+    policy,
+    entity;
+
+    public static AthenzEntityType fromString(String v) {
+        for (AthenzEntityType e : values()) {
+            if (e.toString().equals(v)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("Invalid string representation for AthenzEntityType: " + v);
+    }
+}
+
diff --git a/core/msd/src/main/java/com/yahoo/athenz/msd/MSDSchema.java b/core/msd/src/main/java/com/yahoo/athenz/msd/MSDSchema.java
index 654cadcaa..7bbbbeed7 100644
--- a/core/msd/src/main/java/com/yahoo/athenz/msd/MSDSchema.java
+++ b/core/msd/src/main/java/com/yahoo/athenz/msd/MSDSchema.java
@@ -18,6 +18,36 @@ public class MSDSchema {
         sb.namespace("com.yahoo.athenz.msd");
         sb.comment("Copyright The Athenz Authors Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. The Micro Segmentation Defense (MSD) API");
 
+        sb.enumType("AthenzEntityAction")
+            .comment("Copyright The Athenz Authors Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.")
+            .element("create")
+            .element("delete")
+            .element("update");
+
+        sb.enumType("AthenzEntityType")
+            .element("domain")
+            .element("role")
+            .element("group")
+            .element("service")
+            .element("policy")
+            .element("entity");
+
+        sb.enumType("AthenzDependencyResponseStatus")
+            .element("allow")
+            .element("deny");
+
+        sb.structType("AthenzDependencyRequest")
+            .field("operation", "AthenzEntityAction", false, "domain action like, create/delete/...")
+            .field("domainName", "String", false, "domain name")
+            .field("objectType", "AthenzEntityType", false, "entity type like, domain/role/...")
+            .field("objectName", "String", false, "name of the athenz entity")
+            .field("principal", "String", true, "principal of the executing operation")
+            .field("provider", "String", true, "provider service identity");
+
+        sb.structType("AthenzDependencyResponse")
+            .field("status", "AthenzDependencyResponseStatus", false, "whether the request is allowed or denied")
+            .field("message", "String", true, "reason for denial");
+
         sb.stringType("SimpleName")
             .comment("Copyright The Athenz Authors Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. Common name types used by several API definitions A simple identifier, an element of compound name.")
             .pattern("[a-zA-Z0-9_][a-zA-Z0-9_-]*");
@@ -584,6 +614,20 @@ public class MSDSchema {
             .mapField("annotations", "rdl.ExtendedAnnotation", "String", true, "additional annotations starting with \"x_\"");
 
 
+        sb.resource("AthenzDependencyRequest", "POST", "/domains/dependency-check")
+            .comment("Athenz entity Dependency API webhook")
+            .input("request", "AthenzDependencyRequest", "request")
+            .auth("", "", true)
+            .expected("OK")
+            .exception("BAD_REQUEST", "ResourceError", "")
+
+            .exception("INTERNAL_SERVER_ERROR", "ResourceError", "")
+
+            .exception("NOT_FOUND", "ResourceError", "")
+
+            .exception("UNAUTHORIZED", "ResourceError", "")
+;
+
         sb.resource("TransportPolicyRules", "GET", "/transportpolicies")
             .comment("API endpoint to get the transport policy rules defined in Athenz")
             .headerParam("If-None-Match", "matchingTag", "String", null, "Retrieved from the previous request, this timestamp specifies to the server to return any policies modified since this time")
diff --git a/core/msd/src/main/rdl/Domain.rdli b/core/msd/src/main/rdl/Domain.rdli
new file mode 100644
index 000000000..2f1a8e80a
--- /dev/null
+++ b/core/msd/src/main/rdl/Domain.rdli
@@ -0,0 +1,20 @@
+// Copyright The Athenz Authors
+// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.
+
+include "Domain.tdl";
+
+// Athenz entity Dependency API webhook
+resource AthenzDependencyResponse POST "/domains/dependency-check" {
+    AthenzDependencyRequest request; // request
+
+    authenticate;
+
+    expected OK;
+
+    exceptions {
+        ResourceError INTERNAL_SERVER_ERROR;
+        ResourceError BAD_REQUEST;
+        ResourceError NOT_FOUND;
+        ResourceError UNAUTHORIZED;
+    }
+}
diff --git a/core/msd/src/main/rdl/Domain.tdl b/core/msd/src/main/rdl/Domain.tdl
new file mode 100644
index 000000000..6ea490d8b
--- /dev/null
+++ b/core/msd/src/main/rdl/Domain.tdl
@@ -0,0 +1,20 @@
+// Copyright The Athenz Authors
+// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.
+
+type AthenzEntityAction Enum { create, delete, update }
+type AthenzEntityType Enum { domain, role, group, service, policy, entity }
+type AthenzDependencyResponseStatus Enum { allow, deny }
+
+type AthenzDependencyRequest Struct {
+    AthenzEntityAction operation;   // domain action like, create/delete/...
+    String domainName;              // domain name
+    AthenzEntityType objectType;    // entity type like, domain/role/...
+    String objectName;              // name of the athenz entity
+    String principal (optional);    // principal of the executing operation
+    String provider (optional);     // provider service identity
+}
+
+type AthenzDependencyResponse Struct {
+    AthenzDependencyResponseStatus status;  // whether the request is allowed or denied
+    String message (optional);              // reason for denial
+}
\ No newline at end of file
diff --git a/core/msd/src/main/rdl/MSD.rdl b/core/msd/src/main/rdl/MSD.rdl
index 28b898ed8..545d769c9 100644
--- a/core/msd/src/main/rdl/MSD.rdl
+++ b/core/msd/src/main/rdl/MSD.rdl
@@ -8,6 +8,7 @@ name MSD;
 version 1;
 namespace com.yahoo.athenz.msd;
 
+include "Domain.rdli";
 include "TransportPolicyRule.rdli";
 include "Workload.rdli";
 include "NetworkPolicy.rdli";
diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/AuthHistory.java b/core/zms/src/main/java/com/yahoo/athenz/zms/AuthHistory.java
index d8d6501ba..b0fd1d071 100644
--- a/core/zms/src/main/java/com/yahoo/athenz/zms/AuthHistory.java
+++ b/core/zms/src/main/java/com/yahoo/athenz/zms/AuthHistory.java
@@ -4,6 +4,7 @@
 
 package com.yahoo.athenz.zms;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.yahoo.rdl.*;
 
 //
@@ -11,10 +12,20 @@ import com.yahoo.rdl.*;
 //
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class AuthHistory {
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     public String uriDomain;
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     public String principalDomain;
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     public String principalName;
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     public Timestamp timestamp;
+    @RdlOptional
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     public String endpoint;
     public long ttl;
 
diff --git a/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java b/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java
index 522244f87..c37a26836 100644
--- a/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java
+++ b/core/zms/src/main/java/com/yahoo/athenz/zms/ZMSSchema.java
@@ -565,11 +565,11 @@ public class ZMSSchema {
             .arrayField("validValues", "String", false, "list of valid values for attribute");
 
         sb.structType("AuthHistory")
-            .field("uriDomain", "DomainName", false, "Name of the domain from URI")
-            .field("principalDomain", "DomainName", false, "Principal domain")
-            .field("principalName", "SimpleName", false, "Principal name")
-            .field("timestamp", "Timestamp", false, "Last authorization event timestamp")
-            .field("endpoint", "String", false, "Last authorization endpoint used")
+            .field("uriDomain", "DomainName", true, "Name of the domain from URI")
+            .field("principalDomain", "DomainName", true, "Principal domain")
+            .field("principalName", "SimpleName", true, "Principal name")
+            .field("timestamp", "Timestamp", true, "Last authorization event timestamp")
+            .field("endpoint", "String", true, "Last authorization endpoint used")
             .field("ttl", "Int64", false, "Time until the record will expire");
 
         sb.structType("AuthHistoryDependencies")
diff --git a/core/zms/src/main/rdl/Domain.rdli b/core/zms/src/main/rdl/Domain.rdli
index 3c9f35f28..461b4fb47 100644
--- a/core/zms/src/main/rdl/Domain.rdli
+++ b/core/zms/src/main/rdl/Domain.rdli
@@ -307,11 +307,11 @@ resource DomainMetaStoreValidValuesList GET "/domain/metastore?attribute={attrib
 }
 
 type AuthHistory Struct {
-    DomainName uriDomain; // Name of the domain from URI
-    DomainName principalDomain; // Principal domain
-    SimpleName principalName; // Principal name
-    Timestamp timestamp; // Last authorization event timestamp
-    String endpoint; // Last authorization endpoint used
+    DomainName uriDomain (optional); // Name of the domain from URI
+    DomainName principalDomain (optional); // Principal domain
+    SimpleName principalName (optional); // Principal name
+    Timestamp timestamp (optional); // Last authorization event timestamp
+    String endpoint (optional); // Last authorization endpoint used
     Int64 ttl; // Time until the record will expire
 }
 
diff --git a/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProvider.java b/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProvider.java
index 52391eb1e..276bbbcb1 100644
--- a/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProvider.java
+++ b/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProvider.java
@@ -17,7 +17,10 @@
 package com.yahoo.athenz.instance.provider.impl;
 
 import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
+import com.yahoo.athenz.auth.Authorizer;
 import com.yahoo.athenz.auth.KeyStore;
+import com.yahoo.athenz.auth.Principal;
+import com.yahoo.athenz.auth.impl.SimplePrincipal;
 import com.yahoo.athenz.common.server.db.RolesProvider;
 import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigBoolean;
 import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigCsv;
@@ -54,6 +57,7 @@ public class InstanceGCPProvider implements InstanceProvider {
     static final String GCP_PROP_GKE_CLUSTER_NAMES        = "athenz.zts.gcp_gke_cluster_names";
     static final String GCP_PROP_VALIDATE_IP_ADDRESS      = "athenz.zts.gcp_validate_ip_address";
     static final String GCP_PROP_ALLOWED_IP_ADDRESSES     = "athenz.zts.gcp_allowed_ip_addresses";
+    static final String GCP_ASSUME_IDENTITY_ACTION        = "gcp.assume_identity";
 
     DynamicConfigLong bootTimeOffsetSeconds; // boot time offset in seconds
     long certValidityTime;                   // cert validity for STS creds only case
@@ -66,6 +70,7 @@ public class InstanceGCPProvider implements InstanceProvider {
     RolesProvider rolesProvider = null;
     DynamicConfigBoolean validateIPAddress;
     List<IPBlock> systemAllowedIPAddresses;
+    Authorizer authorizer = null;
 
     public long getTimeOffsetInMilli() {
         return bootTimeOffsetSeconds.get() * 1000;
@@ -145,6 +150,11 @@ public class InstanceGCPProvider implements InstanceProvider {
         this.externalCredentialsProvider = externalCredentialsProvider;
     }
 
+    @Override
+    public void setAuthorizer(Authorizer authorizer) {
+        this.authorizer = authorizer;
+    }
+
     protected Set<String> getDnsSuffixes() {
         return dnsSuffixes;
     }
@@ -288,7 +298,7 @@ public class InstanceGCPProvider implements InstanceProvider {
         // we are using gcpProject name instead of domain name here since there is a 1:1
         // mapping between gcp project and Athenz domain.
 
-        validateAthenzService(derivedAttestationData, instanceService, gcpProject);
+        validateAthenzService(derivedAttestationData, instanceService, gcpProject, instanceDomain);
 
         // validate the IP address in the certificate if enabled
 
@@ -345,7 +355,7 @@ public class InstanceGCPProvider implements InstanceProvider {
         // validate that the domain/service given in the confirmation
         // request match the attestation data
 
-        validateAthenzService(derivedAttestationData, instanceService, gcpProject);
+        validateAthenzService(derivedAttestationData, instanceService, gcpProject, instanceDomain);
 
         // validate the IP address in the certificate if enabled
 
@@ -410,8 +420,8 @@ public class InstanceGCPProvider implements InstanceProvider {
         }
     }
 
-    private void validateAthenzService(GCPDerivedAttestationData derivedAttestationData, String instanceService,
-            String gcpProject) throws ProviderResourceException {
+    private void validateAthenzService(GCPDerivedAttestationData derivedAttestationData, final String instanceService,
+            final String gcpProject, final String instanceDomain) throws ProviderResourceException {
 
         // validate that the gcp project/service given in the confirmation
         // request match the attestation data
@@ -421,7 +431,25 @@ public class InstanceGCPProvider implements InstanceProvider {
         final String serviceName = gcpProject + "." + instanceService;
         final String attestedServiceName = gcpUtils.getServiceNameFromAttestedData(derivedAttestationData);
         if (!serviceName.equals(attestedServiceName)) {
-            throw error("Service name mismatch: attested=" + attestedServiceName + " vs. requested=" + serviceName);
+
+            // if the values don't match, we're going to perform an authorization
+            // check to see if the principal is authorized to request on behalf
+            // of the given service. this will allow us to support cases where
+            // multiple gcp services are allowed to assume the same athenz service
+
+            Principal principal = SimplePrincipal.create(instanceDomain, instanceService, (String) null);
+
+            // for the resource we're going to use the same resource as we do for the
+            // gcp.assume_service action: {athenz-domain}:services/{gcp-service-name}
+
+            if (!attestedServiceName.startsWith(gcpProject + ".")) {
+                throw error("Attested service name: " + attestedServiceName + " does not start with expected project name: " + gcpProject);
+            }
+            final String resource = instanceDomain + ":services/" + attestedServiceName.substring(gcpProject.length() + 1);
+            boolean accessCheck = authorizer.access(GCP_ASSUME_IDENTITY_ACTION, resource, principal, null);
+            if (!accessCheck) {
+                throw error("Service name mismatch: attested=" + attestedServiceName + " vs. requested=" + serviceName);
+            }
         }
     }
 
diff --git a/servers/zms/conf/zms.properties b/servers/zms/conf/zms.properties
index 6473f8f44..3d2b6a8a1 100644
--- a/servers/zms/conf/zms.properties
+++ b/servers/zms/conf/zms.properties
@@ -610,3 +610,9 @@ athenz.zms.no_auth_uri_list=/zms/v1/schema
 
 # A flag indicating whether to automatically delete the tenant assume role assertions when the provider role is deleted.
 #athenz.zms.auto_delete_tenant_assume_role_assertions=false
+
+# A flag indicating whether to skip reporting error metrics. By default, the server reports
+# error metrics with name format: ERROR, error_{error_code}, and {caller}_error_{error_code}.
+# Setting this property to true well disable reporting these metrics to skip unnecessary
+# metrics if the administrator is never looking at them. The default value is false.
+#athenz.zms.skip_error_metrics=false
diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java
index 089334faf..73265eec6 100644
--- a/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java
+++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java
@@ -78,6 +78,7 @@ public final class ZMSConsts {
     public static final String ZMS_PROP_DOMAIN_CONTACT_TYPES    = "athenz.zms.domain_contact_types";
     public static final String ZMS_PROP_DOMAIN_ENVIRONMENTS     = "athenz.zms.domain_environments";
     public static final String ZMS_DEFAULT_DOMAIN_ENVIRONMENTS  = "production,integration,staging,sandbox,qa,development";
+    public static final String ZMS_PROP_SKIP_ERROR_METRICS      = "athenz.zms.skip_error_metrics";
 
     public static final String ZMS_PROP_DEFAULT_MAX_USER_EXPIRY  = "athenz.zms.default_max_user_expiry_days";
     public static final String ZMS_PROP_DEFAULT_MAX_SERVICE_EXPIRY  = "athenz.zms.default_max_service_expiry_days";
diff --git a/servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java b/servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java
index 9da52ca6a..63db1e8d0 100644
--- a/servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java
+++ b/servers/zms/src/main/java/com/yahoo/athenz/zms/utils/ZMSUtils.java
@@ -39,6 +39,7 @@ import java.util.concurrent.TimeUnit;
 public class ZMSUtils {
 
     private static final Logger LOG = LoggerFactory.getLogger(ZMSUtils.class);
+    private static final boolean SKIP_ERROR_METRICS = Boolean.parseBoolean(System.getProperty(ZMSConsts.ZMS_PROP_SKIP_ERROR_METRICS, "false"));
 
     public static void addAssertion(Policy policy, String resource, String action, String role,
             AssertionEffect effect) {
@@ -194,7 +195,7 @@ public class ZMSUtils {
         
         // emit our metrics if configured. the method will automatically
         // return from the caller if caller is null
-        
+
         emitMonmetricError(code, caller);
         return new ResourceException(code, new ResourceError().code(code).message(msg));
     }
@@ -228,19 +229,16 @@ public class ZMSUtils {
     }
     
     public static boolean emitMonmetricError(int errorCode, String caller) {
-        if (errorCode < 1) {
-            return false;
-        }
-        if (StringUtil.isEmpty(caller)) {
-            return false;
-        }
-        if (ZMSImpl.metric == null) {
+
+        if (SKIP_ERROR_METRICS || errorCode < 1 || ZMSImpl.metric == null || StringUtil.isEmpty(caller)) {
             return false;
         }
+
         // Set 3 scoreboard error metrics:
         // (1) cumulative "ERROR" (of all zms request and error types)
         // (2) cumulative granular zms request and error type (eg- "getdomainlist_error_400")
         // (3) cumulative error type (of all zms requests) (eg- "error_404")
+
         String errCode = Integer.toString(errorCode);
         ZMSImpl.metric.increment("ERROR");
         ZMSImpl.metric.increment(caller.toLowerCase() + "_error_" + errCode);
diff --git a/servers/zts/conf/zts.properties b/servers/zts/conf/zts.properties
index a806c7bcc..7e19e6a8e 100644
--- a/servers/zts/conf/zts.properties
+++ b/servers/zts/conf/zts.properties
@@ -851,3 +851,9 @@ athenz.zts.k8s_provider_distribution_validator_factory_class=com.yahoo.athenz.in
 # the authorization is carried out against the principal but then each
 # endpoint carries out the necessary role based checks.
 #athenz.zts.role_based_authz_support=false
+
+# A flag indicating whether to skip reporting error metrics. By default, the server reports
+# error metrics with name format: ERROR, error_{error_code}, and {caller}_error_{error_code}.
+# Setting this property to true well disable reporting these metrics to skip unnecessary
+# metrics if the administrator is never looking at them. The default value is false.
+#athenz.zts.skip_error_metrics=false
diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java
index 4bfd58f6c..7713fca00 100644
--- a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java
+++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java
@@ -54,6 +54,7 @@ public final class ZTSConsts {
     public static final String ZTS_PROP_READ_ONLY_MODE         = "athenz.zts.read_only_mode";
     public static final String ZTS_PROP_HEALTH_CHECK_PATH      = "athenz.zts.health_check_path";
     public static final String ZTS_PROP_SERVER_REGION          = "athenz.zts.server_region";
+    public static final String ZTS_PROP_SKIP_ERROR_METRICS     = "athenz.zts.skip_error_metrics";
 
     public static final String ZTS_PROP_AWS_CREDS_CACHE_TIMEOUT             = "athenz.zts.aws_creds_cache_timeout";
     public static final String ZTS_PROP_AWS_CREDS_INVALID_CACHE_TIMEOUT     = "athenz.zts.aws_creds_invalid_cache_timeout";
diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java
index 5bf613130..7bbd5eced 100644
--- a/servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java
+++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/utils/ZTSUtils.java
@@ -64,8 +64,12 @@ public class ZTSUtils {
     public static final List<String> ZTS_CERT_DNS_SUFFIX = Arrays.asList(
             System.getProperty(ZTSConsts.ZTS_PROP_CERT_DNS_SUFFIX, ZTSConsts.ZTS_CERT_DNS_SUFFIX).split(","));
 
-    public static final long CERT_PRIORITY_MIN_PERCENT_LOW_PRIORITY = Long.parseLong(System.getProperty(ZTSConsts.ZTS_PROP_CERT_PRIORITY_MIN_PERCENT_LOW_PRIORITY, ZTSConsts.ZTS_CERT_PRIORITY_MIN_PERCENT_LOW_PRIORITY_DEFAULT));
-    public static final long CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY = Long.parseLong(System.getProperty(ZTSConsts.ZTS_PROP_CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY, ZTSConsts.ZTS_CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY_DEFAULT));
+    public static final long CERT_PRIORITY_MIN_PERCENT_LOW_PRIORITY = Long.parseLong(System.getProperty(
+            ZTSConsts.ZTS_PROP_CERT_PRIORITY_MIN_PERCENT_LOW_PRIORITY, ZTSConsts.ZTS_CERT_PRIORITY_MIN_PERCENT_LOW_PRIORITY_DEFAULT));
+    public static final long CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY = Long.parseLong(System.getProperty(
+            ZTSConsts.ZTS_PROP_CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY, ZTSConsts.ZTS_CERT_PRIORITY_MAX_PERCENT_HIGH_PRIORITY_DEFAULT));
+    private static final boolean SKIP_ERROR_METRICS = Boolean.parseBoolean(System.getProperty(
+            ZTSConsts.ZTS_PROP_SKIP_ERROR_METRICS, "false"));
 
     private static final String ATHENZ_PROP_KEYSTORE_PATH                    = "athenz.ssl_key_store";
     private static final String ATHENZ_PROP_KEYSTORE_TYPE                    = "athenz.ssl_key_store_type";
@@ -166,10 +170,7 @@ public class ZTSUtils {
     public static boolean emitMonmetricError(int errorCode, String caller,
             String requestDomain, String principalDomain, Metric metric) {
 
-        if (errorCode < 1) {
-            return false;
-        }
-        if (StringUtil.isEmpty(caller)) {
+        if (SKIP_ERROR_METRICS || errorCode < 1 || StringUtil.isEmpty(caller)) {
             return false;
         }
 
diff --git a/ui/package-lock.json b/ui/package-lock.json
index b684b8d5d..cccab0166 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -22,7 +22,7 @@
                 "@reduxjs/toolkit": "1.8.5",
                 "async": "3.2.4",
                 "aws-sdk": "2.1680.0",
-                "axios": "1.8.2",
+                "axios": "1.12.2",
                 "body-parser": "1.20.3",
                 "cookie": "0.7.1",
                 "cookie-parser": "1.4.7",
@@ -1960,9 +1960,9 @@
             }
         },
         "node_modules/@jest/reporters/node_modules/brace-expansion": {
-            "version": "1.1.11",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "version": "1.1.12",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+            "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -4729,13 +4729,12 @@
             }
         },
         "node_modules/axios": {
-            "version": "1.8.2",
-            "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
-            "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
-            "license": "MIT",
+            "version": "1.12.2",
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+            "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
             "dependencies": {
                 "follow-redirects": "^1.15.6",
-                "form-data": "^4.0.0",
+                "form-data": "^4.0.4",
                 "proxy-from-env": "^1.1.0"
             }
         },
@@ -5011,9 +5010,9 @@
             "license": "ISC"
         },
         "node_modules/brace-expansion": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-            "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+            "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
             "dev": true,
             "dependencies": {
                 "balanced-match": "^1.0.0"
@@ -5206,6 +5205,18 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/call-bind-apply-helpers": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+            "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+            "dependencies": {
+                "es-errors": "^1.3.0",
+                "function-bind": "^1.1.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/callsites": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -6858,6 +6869,19 @@
                 "react": ">=16.12.0"
             }
         },
+        "node_modules/dunder-proto": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+            "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+            "dependencies": {
+                "call-bind-apply-helpers": "^1.0.1",
+                "es-errors": "^1.3.0",
+                "gopd": "^1.2.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/duplexer2": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
@@ -7108,12 +7132,9 @@
             }
         },
         "node_modules/es-define-property": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
-            "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
-            "dependencies": {
-                "get-intrinsic": "^1.2.4"
-            },
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+            "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
             "engines": {
                 "node": ">= 0.4"
             }
@@ -7152,6 +7173,31 @@
             "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
             "dev": true
         },
+        "node_modules/es-object-atoms": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+            "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+            "dependencies": {
+                "es-errors": "^1.3.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "node_modules/es-set-tostringtag": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+            "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+            "dependencies": {
+                "es-errors": "^1.3.0",
+                "get-intrinsic": "^1.2.6",
+                "has-tostringtag": "^1.0.2",
+                "hasown": "^2.0.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/es5-ext": {
             "version": "0.10.64",
             "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
@@ -8120,12 +8166,14 @@
             }
         },
         "node_modules/form-data": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-            "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+            "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
             "dependencies": {
                 "asynckit": "^0.4.0",
                 "combined-stream": "^1.0.8",
+                "es-set-tostringtag": "^2.1.0",
+                "hasown": "^2.0.2",
                 "mime-types": "^2.1.12"
             },
             "engines": {
@@ -8321,15 +8369,20 @@
             }
         },
         "node_modules/get-intrinsic": {
-            "version": "1.2.4",
-            "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
-            "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+            "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
             "dependencies": {
+                "call-bind-apply-helpers": "^1.0.2",
+                "es-define-property": "^1.0.1",
                 "es-errors": "^1.3.0",
+                "es-object-atoms": "^1.1.1",
                 "function-bind": "^1.1.2",
-                "has-proto": "^1.0.1",
-                "has-symbols": "^1.0.3",
-                "hasown": "^2.0.0"
+                "get-proto": "^1.0.1",
+                "gopd": "^1.2.0",
+                "has-symbols": "^1.1.0",
+                "hasown": "^2.0.2",
+                "math-intrinsics": "^1.1.0"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -8358,6 +8411,18 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/get-proto": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+            "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+            "dependencies": {
+                "dunder-proto": "^1.0.1",
+                "es-object-atoms": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/get-ready": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/get-ready/-/get-ready-1.0.0.tgz",
@@ -8468,11 +8533,11 @@
             }
         },
         "node_modules/gopd": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
-            "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-            "dependencies": {
-                "get-intrinsic": "^1.1.3"
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+            "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
@@ -8558,21 +8623,10 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/has-proto": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
-            "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
-            "engines": {
-                "node": ">= 0.4"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/has-symbols": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-            "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+            "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
             "engines": {
                 "node": ">= 0.4"
             },
@@ -9811,9 +9865,9 @@
             }
         },
         "node_modules/jest-config/node_modules/brace-expansion": {
-            "version": "1.1.11",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "version": "1.1.12",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+            "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -10552,9 +10606,9 @@
             }
         },
         "node_modules/jest-runtime/node_modules/brace-expansion": {
-            "version": "1.1.11",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "version": "1.1.12",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+            "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -11322,6 +11376,14 @@
                 "tmpl": "1.0.5"
             }
         },
+        "node_modules/math-intrinsics": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+            "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/media-typer": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -14544,11 +14606,10 @@
             "dev": true
         },
         "node_modules/tar-fs": {
-            "version": "3.0.8",
-            "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
-            "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
+            "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
             "dev": true,
-            "license": "MIT",
             "dependencies": {
                 "pump": "^3.0.0",
                 "tar-stream": "^3.1.5"
@@ -14713,9 +14774,9 @@
             }
         },
         "node_modules/test-exclude/node_modules/brace-expansion": {
-            "version": "1.1.11",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "version": "1.1.12",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+            "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -17187,9 +17248,9 @@
             },
             "dependencies": {
                 "brace-expansion": {
-                    "version": "1.1.11",
-                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+                    "version": "1.1.12",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+                    "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -19270,12 +19331,12 @@
             }
         },
         "axios": {
-            "version": "1.8.2",
-            "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
-            "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
+            "version": "1.12.2",
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+            "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
             "requires": {
                 "follow-redirects": "^1.15.6",
-                "form-data": "^4.0.0",
+                "form-data": "^4.0.4",
                 "proxy-from-env": "^1.1.0"
             }
         },
@@ -19469,9 +19530,9 @@
             "dev": true
         },
         "brace-expansion": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-            "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+            "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
             "dev": true,
             "requires": {
                 "balanced-match": "^1.0.0"
@@ -19610,6 +19671,15 @@
                 "set-function-length": "^1.2.1"
             }
         },
+        "call-bind-apply-helpers": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+            "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+            "requires": {
+                "es-errors": "^1.3.0",
+                "function-bind": "^1.1.2"
+            }
+        },
         "callsites": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -20791,6 +20861,16 @@
                 "tslib": "^2.3.0"
             }
         },
+        "dunder-proto": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+            "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+            "requires": {
+                "call-bind-apply-helpers": "^1.0.1",
+                "es-errors": "^1.3.0",
+                "gopd": "^1.2.0"
+            }
+        },
         "duplexer2": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
@@ -20990,12 +21070,9 @@
             }
         },
         "es-define-property": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
-            "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
-            "requires": {
-                "get-intrinsic": "^1.2.4"
-            }
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+            "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
         },
         "es-errors": {
             "version": "1.3.0",
@@ -21027,6 +21104,25 @@
                 }
             }
         },
+        "es-object-atoms": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+            "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+            "requires": {
+                "es-errors": "^1.3.0"
+            }
+        },
+        "es-set-tostringtag": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+            "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+            "requires": {
+                "es-errors": "^1.3.0",
+                "get-intrinsic": "^1.2.6",
+                "has-tostringtag": "^1.0.2",
+                "hasown": "^2.0.2"
+            }
+        },
         "es5-ext": {
             "version": "0.10.64",
             "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
@@ -21732,12 +21828,14 @@
             }
         },
         "form-data": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-            "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+            "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
             "requires": {
                 "asynckit": "^0.4.0",
                 "combined-stream": "^1.0.8",
+                "es-set-tostringtag": "^2.1.0",
+                "hasown": "^2.0.2",
                 "mime-types": "^2.1.12"
             }
         },
@@ -21872,15 +21970,20 @@
             "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
         },
         "get-intrinsic": {
-            "version": "1.2.4",
-            "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
-            "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+            "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
             "requires": {
+                "call-bind-apply-helpers": "^1.0.2",
+                "es-define-property": "^1.0.1",
                 "es-errors": "^1.3.0",
+                "es-object-atoms": "^1.1.1",
                 "function-bind": "^1.1.2",
-                "has-proto": "^1.0.1",
-                "has-symbols": "^1.0.3",
-                "hasown": "^2.0.0"
+                "get-proto": "^1.0.1",
+                "gopd": "^1.2.0",
+                "has-symbols": "^1.1.0",
+                "hasown": "^2.0.2",
+                "math-intrinsics": "^1.1.0"
             }
         },
         "get-package-type": {
@@ -21894,6 +21997,15 @@
             "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==",
             "dev": true
         },
+        "get-proto": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+            "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+            "requires": {
+                "dunder-proto": "^1.0.1",
+                "es-object-atoms": "^1.0.0"
+            }
+        },
         "get-ready": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/get-ready/-/get-ready-1.0.0.tgz",
@@ -21977,12 +22089,9 @@
             "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
         },
         "gopd": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
-            "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-            "requires": {
-                "get-intrinsic": "^1.1.3"
-            }
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+            "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
         },
         "got": {
             "version": "11.8.6",
@@ -22047,15 +22156,10 @@
                 "es-define-property": "^1.0.0"
             }
         },
-        "has-proto": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
-            "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q=="
-        },
         "has-symbols": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-            "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+            "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
         },
         "has-tostringtag": {
             "version": "1.0.2",
@@ -22921,9 +23025,9 @@
             },
             "dependencies": {
                 "brace-expansion": {
-                    "version": "1.1.11",
-                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+                    "version": "1.1.12",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+                    "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -23502,9 +23606,9 @@
             },
             "dependencies": {
                 "brace-expansion": {
-                    "version": "1.1.11",
-                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+                    "version": "1.1.12",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+                    "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -24113,6 +24217,11 @@
                 "tmpl": "1.0.5"
             }
         },
+        "math-intrinsics": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+            "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
+        },
         "media-typer": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -26444,9 +26553,9 @@
             "dev": true
         },
         "tar-fs": {
-            "version": "3.0.8",
-            "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
-            "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
+            "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
             "dev": true,
             "requires": {
                 "bare-fs": "^4.0.1",
@@ -26589,9 +26698,9 @@
             },
             "dependencies": {
                 "brace-expansion": {
-                    "version": "1.1.11",
-                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+                    "version": "1.1.12",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+                    "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
diff --git a/ui/package.json b/ui/package.json
index 84c7e59f8..766470672 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -58,7 +58,7 @@
         "@reduxjs/toolkit": "1.8.5",
         "async": "3.2.4",
         "aws-sdk": "2.1680.0",
-        "axios": "1.8.2",
+        "axios": "1.12.2",
         "body-parser": "1.20.3",
         "cookie": "0.7.1",
         "cookie-parser": "1.4.7",



Test patch:
diff --git a/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDClientTest.java b/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDClientTest.java
index efbbb7481..cb4939240 100644
--- a/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDClientTest.java
+++ b/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDClientTest.java
@@ -100,6 +100,49 @@ public class MSDClientTest {
         msdClient.close();
     }
 
+    @Test
+    public void testServiceDependencyCheckAllowed() throws Exception {
+        MSDRDLClientMock msdrdlClientMock = new MSDRDLClientMock();
+        MSDClient msdClient = new MSDClient("https://localhost:4443/msd/v1", createDummySslContext());
+        msdClient.client = msdrdlClientMock;
+        AthenzDependencyResponse resp = msdClient.serviceDependencyCheck("dom1", "svc1", AthenzEntityAction.delete);
+        assertNotNull(resp);
+        assertEquals(resp.getStatus(), AthenzDependencyResponseStatus.allow);
+        assertEquals(resp.getMessage(), "allowed");
+        msdClient.close();
+    }
+
+    @Test
+    public void testServiceDependencyCheckDenied() throws Exception {
+        MSDRDLClientMock msdrdlClientMock = new MSDRDLClientMock();
+        MSDClient msdClient = new MSDClient("https://localhost:4443/msd/v1", createDummySslContext());
+        msdClient.client = msdrdlClientMock;
+        AthenzDependencyResponse resp = msdClient.serviceDependencyCheck("deny-domain", "svc1", AthenzEntityAction.delete);
+        assertNotNull(resp);
+        assertEquals(resp.getStatus(), AthenzDependencyResponseStatus.deny);
+        assertEquals(resp.getMessage(), "denied");
+        msdClient.close();
+    }
+
+    @Test
+    public void testServiceDependencyCheckException() throws Exception {
+        MSDRDLClientMock msdrdlClientMock = new MSDRDLClientMock();
+        MSDClient msdClient = new MSDClient("https://localhost:4443/msd/v1", createDummySslContext());
+        msdClient.client = msdrdlClientMock;
+        try {
+            msdClient.serviceDependencyCheck("bad-domain", "svc1", AthenzEntityAction.delete);
+        } catch (ClientResourceException re) {
+            assertEquals(re.getCode(), 404);
+            assertEquals(re.getMessage(), "ClientResourceException (404): unknown domain");
+        }
+        try {
+            msdClient.serviceDependencyCheck("bad-req", "svc1", AthenzEntityAction.delete);
+        } catch (ClientResourceException re) {
+            assertEquals(re.getCode(), 400);
+            assertEquals(re.getMessage(), "ClientResourceException (400): bad request");
+        }
+        msdClient.close();
+    }
 
     @Test
     public void testGetWorkloadsByIP() throws Exception {
diff --git a/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDRDLClientMock.java b/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDRDLClientMock.java
index 587626ab8..31a838594 100644
--- a/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDRDLClientMock.java
+++ b/clients/java/msd/src/test/java/com/yahoo/athenz/msd/MSDRDLClientMock.java
@@ -16,8 +16,11 @@
 package com.yahoo.athenz.msd;
 
 import com.yahoo.rdl.Timestamp;
+import org.apache.commons.codec.binary.StringUtils;
 
 import java.io.Closeable;
+import java.io.IOException;
+import java.net.URISyntaxException;
 import java.util.Collections;
 import java.util.List;
 
@@ -132,4 +135,18 @@ public class MSDRDLClientMock extends MSDRDLGeneratedClient implements Closeable
         }
         return null;
     }
+
+    @Override
+    public AthenzDependencyResponse postAthenzDependencyRequest(AthenzDependencyRequest request) throws URISyntaxException, IOException  {
+        if (null != request && StringUtils.equals("bad-domain", request.getDomainName())) {
+            throw new ClientResourceException(404, "unknown domain");
+        }
+        if (null != request && StringUtils.equals("bad-req", request.getDomainName())) {
+            throw new RuntimeException("bad request");
+        }
+        if (null != request && StringUtils.equals("deny-domain", request.getDomainName())) {
+            return new AthenzDependencyResponse().setStatus(AthenzDependencyResponseStatus.deny).setMessage("denied");
+        }
+        return new AthenzDependencyResponse().setStatus(AthenzDependencyResponseStatus.allow).setMessage("allowed");
+    }
 }
diff --git a/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyRequestTest.java b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyRequestTest.java
new file mode 100644
index 000000000..867723d24
--- /dev/null
+++ b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyRequestTest.java
@@ -0,0 +1,134 @@
+/*
+ *  Copyright The Athenz Authors
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.yahoo.athenz.msd;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AthenzDependencyRequestTest {
+
+    @Test
+    public void testAthenzDependencyRequestFields() {
+        AthenzDependencyRequest req1 = new AthenzDependencyRequest();
+        req1.setDomainName("domain1");
+        req1.setObjectName("svc1");
+        req1.setObjectType(AthenzEntityType.service);
+        req1.setOperation(AthenzEntityAction.create);
+        req1.setPrincipal("p123");
+        req1.setProvider("athenz");
+
+        assertNotNull(req1);
+        assertFalse(req1.equals(new Object()));
+        assertEquals(req1.getDomainName(), "domain1");
+        assertEquals(req1.getObjectName(), "svc1");
+        assertEquals(req1.getObjectType(), AthenzEntityType.service);
+        assertEquals(req1.getOperation(), AthenzEntityAction.create);
+        assertEquals(req1.getPrincipal(), "p123");
+        assertEquals(req1.getProvider(), "athenz");
+        assertFalse(req1.equals(null));
+        assertFalse(req1.equals(new Object()));
+    }
+
+    @Test(dataProvider = "dataForTestAthenzDependencyRequestEquality")
+    public void testAthenzDependencyRequestEquality(AthenzDependencyRequest req1, AthenzDependencyRequest req2,
+                                                    boolean expected) {
+        assertEquals(req1.equals(req2), expected);
+    }
+
+    @DataProvider
+    private Object[][] dataForTestAthenzDependencyRequestEquality() {
+        AthenzDependencyRequest req1 = new AthenzDependencyRequest();
+        req1.setDomainName("domain1");
+        req1.setObjectName("svc1");
+        req1.setObjectType(AthenzEntityType.service);
+        req1.setOperation(AthenzEntityAction.create);
+        req1.setPrincipal("p123");
+        req1.setProvider("athenz");
+
+        AthenzDependencyRequest req2 = new AthenzDependencyRequest();
+        req2.setDomainName("domain1");
+        req2.setObjectName("svc1");
+        req2.setObjectType(AthenzEntityType.service);
+        req2.setOperation(AthenzEntityAction.create);
+        req2.setPrincipal("p123");
+        req2.setProvider("athenz");
+
+        AthenzDependencyRequest req3 = new AthenzDependencyRequest();
+        req3.setDomainName("domain2");
+        req3.setObjectName("svc1");
+        req3.setObjectType(AthenzEntityType.service);
+        req3.setOperation(AthenzEntityAction.create);
+        req3.setPrincipal("p123");
+        req3.setProvider("athenz");
+
+        AthenzDependencyRequest req4 = new AthenzDependencyRequest();
+        req4.setDomainName("domain1");
+        req4.setObjectName("svc2");
+        req4.setObjectType(AthenzEntityType.service);
+        req4.setOperation(AthenzEntityAction.create);
+        req4.setPrincipal("p123");
+        req4.setProvider("athenz");
+
+        AthenzDependencyRequest req5 = new AthenzDependencyRequest();
+        req5.setDomainName("domain1");
+        req5.setObjectName("svc1");
+        req5.setObjectType(AthenzEntityType.role);
+        req5.setOperation(AthenzEntityAction.create);
+        req5.setPrincipal("p123");
+        req5.setProvider("athenz");
+
+        AthenzDependencyRequest req6 = new AthenzDependencyRequest();
+        req6.setDomainName("domain1");
+        req6.setObjectName("svc1");
+        req6.setObjectType(AthenzEntityType.service);
+        req6.setOperation(AthenzEntityAction.update);
+        req6.setPrincipal("p123");
+        req6.setProvider("athenz");
+
+        AthenzDependencyRequest req7 = new AthenzDependencyRequest();
+        req7.setDomainName("domain1");
+        req7.setObjectName("svc1");
+        req7.setObjectType(AthenzEntityType.service);
+        req7.setOperation(AthenzEntityAction.create);
+        req7.setPrincipal("p124");
+        req7.setProvider("athenz");
+
+        AthenzDependencyRequest req8 = new AthenzDependencyRequest();
+        req8.setDomainName("domain1");
+        req8.setObjectName("svc1");
+        req8.setObjectType(AthenzEntityType.service);
+        req8.setOperation(AthenzEntityAction.create);
+        req8.setPrincipal("p123");
+        req8.setProvider("msd");
+
+        return new Object[][]{
+            {req1, req1, true},
+            {req1, req2, true},
+            {req1, req3, false},
+            {req1, req4, false},
+            {req1, req5, false},
+            {req1, req6, false},
+            {req1, req7, false},
+            {req1, req8, false},
+            {new AthenzDependencyRequest(), new AthenzDependencyRequest(), true}
+            };
+    }
+}
\ No newline at end of file
diff --git a/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyResponseStatusTest.java b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyResponseStatusTest.java
new file mode 100644
index 000000000..276ee9e2f
--- /dev/null
+++ b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyResponseStatusTest.java
@@ -0,0 +1,48 @@
+/*
+ *  Copyright The Athenz Authors
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.yahoo.athenz.msd;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AthenzDependencyResponseStatusTest {
+
+    @Test(dataProvider = "dataForTestFromString")
+    public void testFromString(String input, AthenzDependencyResponseStatus expected, String expectedError) {
+        AthenzDependencyResponseStatus actual = null;
+        String actualErr = null;
+        try {
+            actual = AthenzDependencyResponseStatus.fromString(input);
+        } catch (Exception ex) {
+            actualErr = ex.getMessage();
+        }
+        assertEquals(actualErr, expectedError, "exception");
+        assertEquals(actual, expected);
+    }
+
+    @DataProvider
+    private Object[][] dataForTestFromString() {
+        return new Object[][]{
+            {null, null, "Invalid string representation for AthenzDependencyResponseStatus: null"},
+            {"something", null, "Invalid string representation for AthenzDependencyResponseStatus: something"},
+            {"allow", AthenzDependencyResponseStatus.allow, null},
+            {"deny", AthenzDependencyResponseStatus.deny, null}
+            };
+    }
+}
\ No newline at end of file
diff --git a/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyResponseTest.java b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyResponseTest.java
new file mode 100644
index 000000000..ffca5fc17
--- /dev/null
+++ b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzDependencyResponseTest.java
@@ -0,0 +1,74 @@
+/*
+ *  Copyright The Athenz Authors
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.yahoo.athenz.msd;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AthenzDependencyResponseTest {
+
+    @Test
+    public void testAthenzDependencyRequestFields() {
+        AthenzDependencyResponse resp1 = new AthenzDependencyResponse();
+        resp1.setStatus(AthenzDependencyResponseStatus.allow);
+        resp1.setMessage("msg1");
+
+        assertNotNull(resp1);
+        assertFalse(resp1.equals(new Object()));
+        assertEquals(resp1.getStatus(), AthenzDependencyResponseStatus.allow);
+        assertEquals(resp1.getMessage(), "msg1");
+        assertFalse(resp1.equals(null));
+        assertFalse(resp1.equals(new Object()));
+    }
+
+    @Test(dataProvider = "dataForTestAthenzDependencyResponseEquality")
+    public void testAthenzDependencyResponseEquality(AthenzDependencyResponse resp1, AthenzDependencyResponse resp2,
+                                                    boolean expected) {
+        assertEquals(resp1.equals(resp2), expected);
+    }
+
+    @DataProvider
+    private Object[][] dataForTestAthenzDependencyResponseEquality() {
+        AthenzDependencyResponse resp1 = new AthenzDependencyResponse();
+        resp1.setStatus(AthenzDependencyResponseStatus.allow);
+        resp1.setMessage("msg1");
+
+        AthenzDependencyResponse resp2 = new AthenzDependencyResponse();
+        resp2.setStatus(AthenzDependencyResponseStatus.allow);
+        resp2.setMessage("msg1");
+
+        AthenzDependencyResponse resp3 = new AthenzDependencyResponse();
+        resp3.setStatus(AthenzDependencyResponseStatus.deny);
+        resp3.setMessage("msg1");
+
+        AthenzDependencyResponse resp4 = new AthenzDependencyResponse();
+        resp4.setStatus(AthenzDependencyResponseStatus.allow);
+        resp4.setMessage("msg2");
+
+        return new Object[][]{
+            {resp1, resp1, true},
+            {resp1, resp2, true},
+            {resp1, resp3, false},
+            {resp1, resp4, false},
+            {new AthenzDependencyResponse(), new AthenzDependencyResponse(), true}
+            };
+    }
+}
\ No newline at end of file
diff --git a/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzEntityActionTest.java b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzEntityActionTest.java
new file mode 100644
index 000000000..5261eb28d
--- /dev/null
+++ b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzEntityActionTest.java
@@ -0,0 +1,49 @@
+/*
+ *  Copyright The Athenz Authors
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.yahoo.athenz.msd;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AthenzEntityActionTest {
+
+    @Test(dataProvider = "dataForTestFromString")
+    public void testFromString(String input, AthenzEntityAction expected, String expectedError) {
+        AthenzEntityAction actual = null;
+        String actualErr = null;
+        try {
+            actual = AthenzEntityAction.fromString(input);
+        } catch (Exception ex) {
+            actualErr = ex.getMessage();
+        }
+        assertEquals(actualErr, expectedError, "exception");
+        assertEquals(actual, expected);
+    }
+
+    @DataProvider
+    private Object[][] dataForTestFromString() {
+        return new Object[][]{
+            {null, null, "Invalid string representation for AthenzEntityAction: null"},
+            {"something", null, "Invalid string representation for AthenzEntityAction: something"},
+            {"create", AthenzEntityAction.create, null},
+            {"delete", AthenzEntityAction.delete, null},
+            {"update", AthenzEntityAction.update, null},
+            };
+    }
+}
\ No newline at end of file
diff --git a/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzEntityTypeTest.java b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzEntityTypeTest.java
new file mode 100644
index 000000000..2664e6599
--- /dev/null
+++ b/core/msd/src/test/java/com/yahoo/athenz/msd/AthenzEntityTypeTest.java
@@ -0,0 +1,52 @@
+/*
+ *  Copyright The Athenz Authors
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.yahoo.athenz.msd;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AthenzEntityTypeTest {
+
+    @Test(dataProvider = "dataForTestFromString")
+    public void testFromString(String input, AthenzEntityType expected, String expectedError) {
+        AthenzEntityType actual = null;
+        String actualErr = null;
+        try {
+            actual = AthenzEntityType.fromString(input);
+        } catch (Exception ex) {
+            actualErr = ex.getMessage();
+        }
+        assertEquals(actualErr, expectedError, "exception");
+        assertEquals(actual, expected);
+    }
+
+    @DataProvider
+    private Object[][] dataForTestFromString() {
+        return new Object[][]{
+            {null, null, "Invalid string representation for AthenzEntityType: null"},
+            {"something", null, "Invalid string representation for AthenzEntityType: something"},
+            {"domain", AthenzEntityType.domain, null},
+            {"role", AthenzEntityType.role, null},
+            {"group", AthenzEntityType.group, null},
+            {"service", AthenzEntityType.service, null},
+            {"policy", AthenzEntityType.policy, null},
+            {"entity", AthenzEntityType.entity, null},
+            };
+    }
+}
\ No newline at end of file
diff --git a/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProviderTest.java b/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProviderTest.java
index 84d6bd8da..0b88a312b 100644
--- a/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProviderTest.java
+++ b/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceGCPProviderTest.java
@@ -18,6 +18,9 @@ package com.yahoo.athenz.instance.provider.impl;
 
 import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
 import com.google.api.client.util.ArrayMap;
+import com.yahoo.athenz.auth.Authorizer;
+import com.yahoo.athenz.auth.Principal;
+import com.yahoo.athenz.auth.impl.SimplePrincipal;
 import com.yahoo.athenz.instance.provider.InstanceConfirmation;
 import com.yahoo.athenz.instance.provider.InstanceProvider;
 import com.yahoo.athenz.instance.provider.ProviderResourceException;
@@ -524,6 +527,136 @@ public class InstanceGCPProviderTest {
         provider.close();
     }
 
+    @Test
+    public void testConfirmInstanceWithServiceNameAuthorization() throws ProviderResourceException {
+
+        Principal principal = SimplePrincipal.create("my.domain", "my-authorized-service", (String) null);
+
+        Authorizer authorizerMock = Mockito.mock(Authorizer.class);
+        Mockito.when(authorizerMock.access(eq("gcp.assume_identity"), eq("my.domain:services/my-service"),
+                        eq(principal), eq(null))).thenReturn(true);
+
+        InstanceGCPProvider provider = new InstanceGCPProvider();
+        System.setProperty(InstanceGCPProvider.GCP_PROP_BOOT_TIME_OFFSET, "60");
+        provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceGCPProvider", null, null);
+        provider.setAuthorizer(authorizerMock);
+
+        GoogleIdToken.Payload dummyPayload = createRecentPayload(true);
+        InstanceGCPUtils gcpUtilsMock = Mockito.mock(InstanceGCPUtils.class);
+        Mockito.when(gcpUtilsMock.validateGCPIdentityToken(anyString(), any(StringBuilder.class))).thenReturn(dummyPayload);
+        Mockito.when(gcpUtilsMock.getProjectIdFromAttestedData(any())).thenReturn("my-gcp-project");
+        Mockito.when(gcpUtilsMock.getGCPRegionFromZone(any())).thenReturn("us-west1");
+        Mockito.when(gcpUtilsMock.getServiceNameFromAttestedData(any())).thenReturn("my-gcp-project.my-service");
+        Mockito.doCallRealMethod().when(gcpUtilsMock).populateAttestationData(any(GoogleIdToken.Payload.class), any(GCPDerivedAttestationData.class));
+        provider.setInstanceGcpUtils(gcpUtilsMock);
+
+        InstanceConfirmation confirmation = new InstanceConfirmation();
+        confirmation.setAttestationData("{\"identityToken\": \"abc\"}");
+        confirmation.setDomain("my.domain");
+        confirmation.setService("my-authorized-service");
+        confirmation.setProvider("gcp.us-west1");
+
+        Map<String, String> attrs = new HashMap<>();
+        attrs.put(ZTS_INSTANCE_GCP_PROJECT, "my-gcp-project");
+        attrs.put(ZTS_INSTANCE_SAN_DNS, "my-authorized-service.my-domain.gcp.athenz.cloud,3692465099344887023.instanceid.athenz.gcp.athenz.cloud");
+        attrs.put(ZTS_INSTANCE_SAN_URI, "spiffe://my-domain/sa/my-authorized-service");
+        confirmation.setAttributes(attrs);
+
+        provider.confirmInstance(confirmation);
+
+        System.clearProperty(InstanceGCPProvider.GCP_PROP_BOOT_TIME_OFFSET);
+        provider.close();
+    }
+
+    @Test
+    public void testConfirmInstanceWithNoServiceNameAuthorization() {
+
+        Principal principal = SimplePrincipal.create("my.domain", "my-authorized-service", (String) null);
+
+        Authorizer authorizerMock = Mockito.mock(Authorizer.class);
+        Mockito.when(authorizerMock.access(eq("gcp.assume_identity"), eq("my.domain:services/my-service"),
+                eq(principal), eq(null))).thenReturn(false);
+
+        InstanceGCPProvider provider = new InstanceGCPProvider();
+        System.setProperty(InstanceGCPProvider.GCP_PROP_BOOT_TIME_OFFSET, "60");
+        provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceGCPProvider", null, null);
+        provider.setAuthorizer(authorizerMock);
+
+        GoogleIdToken.Payload dummyPayload = createRecentPayload(true);
+        InstanceGCPUtils gcpUtilsMock = Mockito.mock(InstanceGCPUtils.class);
+        Mockito.when(gcpUtilsMock.validateGCPIdentityToken(anyString(), any(StringBuilder.class))).thenReturn(dummyPayload);
+        Mockito.when(gcpUtilsMock.getProjectIdFromAttestedData(any())).thenReturn("my-gcp-project");
+        Mockito.when(gcpUtilsMock.getGCPRegionFromZone(any())).thenReturn("us-west1");
+        Mockito.when(gcpUtilsMock.getServiceNameFromAttestedData(any())).thenReturn("my-gcp-project.my-service");
+        Mockito.doCallRealMethod().when(gcpUtilsMock).populateAttestationData(any(GoogleIdToken.Payload.class), any(GCPDerivedAttestationData.class));
+        provider.setInstanceGcpUtils(gcpUtilsMock);
+
+        InstanceConfirmation confirmation = new InstanceConfirmation();
+        confirmation.setAttestationData("{\"identityToken\": \"abc\"}");
+        confirmation.setDomain("my.domain");
+        confirmation.setService("my-authorized-service");
+        confirmation.setProvider("gcp.us-west1");
+
+        Map<String, String> attrs = new HashMap<>();
+        attrs.put(ZTS_INSTANCE_GCP_PROJECT, "my-gcp-project");
+        attrs.put(ZTS_INSTANCE_SAN_DNS, "my-authorized-service.my-domain.gcp.athenz.cloud,3692465099344887023.instanceid.athenz.gcp.athenz.cloud");
+        attrs.put(ZTS_INSTANCE_SAN_URI, "spiffe://my-domain/sa/my-authorized-service");
+        confirmation.setAttributes(attrs);
+
+        try {
+            provider.confirmInstance(confirmation);
+            fail();
+        } catch (ProviderResourceException ex) {
+            assertEquals(ex.getCode(), 403);
+            assertTrue(ex.getMessage().contains("Service name mismatch"));
+        }
+
+        System.clearProperty(InstanceGCPProvider.GCP_PROP_BOOT_TIME_OFFSET);
+        provider.close();
+    }
+
+    @Test
+    public void testConfirmInstanceWithNMismatchProjectName() {
+
+        Principal principal = SimplePrincipal.create("my.domain", "my-authorized-service", (String) null);
+
+        InstanceGCPProvider provider = new InstanceGCPProvider();
+        System.setProperty(InstanceGCPProvider.GCP_PROP_BOOT_TIME_OFFSET, "60");
+        provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceGCPProvider", null, null);
+
+        GoogleIdToken.Payload dummyPayload = createRecentPayload(true);
+        InstanceGCPUtils gcpUtilsMock = Mockito.mock(InstanceGCPUtils.class);
+        Mockito.when(gcpUtilsMock.validateGCPIdentityToken(anyString(), any(StringBuilder.class))).thenReturn(dummyPayload);
+        Mockito.when(gcpUtilsMock.getProjectIdFromAttestedData(any())).thenReturn("my-gcp-project");
+        Mockito.when(gcpUtilsMock.getGCPRegionFromZone(any())).thenReturn("us-west1");
+        Mockito.when(gcpUtilsMock.getServiceNameFromAttestedData(any())).thenReturn("my-test-gcp-project.my-service");
+        Mockito.doCallRealMethod().when(gcpUtilsMock).populateAttestationData(any(GoogleIdToken.Payload.class), any(GCPDerivedAttestationData.class));
+        provider.setInstanceGcpUtils(gcpUtilsMock);
+
+        InstanceConfirmation confirmation = new InstanceConfirmation();
+        confirmation.setAttestationData("{\"identityToken\": \"abc\"}");
+        confirmation.setDomain("my.domain");
+        confirmation.setService("my-authorized-service");
+        confirmation.setProvider("gcp.us-west1");
+
+        Map<String, String> attrs = new HashMap<>();
+        attrs.put(ZTS_INSTANCE_GCP_PROJECT, "my-gcp-project");
+        attrs.put(ZTS_INSTANCE_SAN_DNS, "my-authorized-service.my-domain.gcp.athenz.cloud,3692465099344887023.instanceid.athenz.gcp.athenz.cloud");
+        attrs.put(ZTS_INSTANCE_SAN_URI, "spiffe://my-domain/sa/my-authorized-service");
+        confirmation.setAttributes(attrs);
+
+        try {
+            provider.confirmInstance(confirmation);
+            fail();
+        } catch (ProviderResourceException ex) {
+            assertEquals(ex.getCode(), 403);
+            assertTrue(ex.getMessage().contains("does not start with expected project name: my-gcp-project"));
+        }
+
+        System.clearProperty(InstanceGCPProvider.GCP_PROP_BOOT_TIME_OFFSET);
+        provider.close();
+    }
+
     @Test
     public void testConfirmInstanceNoAdditionalMetadata() throws ProviderResourceException {