From dcfe413311e1efc6a16a8e727d1b29b285ac8006 Mon Sep 17 00:00:00 2001 From: Henry Avetisyan Date: Fri, 12 Dec 2025 11:17:08 -0800 Subject: [PATCH] support for service jwt svids Signed-off-by: Henry Avetisyan --- .gitignore | 3 + assembly/utils/utils.xml | 4 + clients/go/zts/model.go | 73 +- clients/go/zts/zts_schema.go | 10 +- .../zts/InstanceRegisterInformation.java | 82 ++ .../java/com/yahoo/athenz/zts/ZTSSchema.java | 12 +- core/zts/src/main/rdl/Instance.rdli | 10 +- .../zts/InstanceRegisterInformationTest.java | 60 ++ .../com/yahoo/athenz/auth/token/IdToken.java | 15 +- .../yahoo/athenz/auth/token/IdTokenTest.java | 34 + .../instance/provider/InstanceProvider.java | 16 +- .../impl/InstanceAthenzRBACProvider.java | 168 ++++ .../provider/InstanceProviderTest.java | 1 + .../impl/InstanceAthenzRBACProviderTest.java | 529 +++++++++++ pom.xml | 1 + .../java/com/yahoo/athenz/zts/ZTSImpl.java | 268 ++++-- .../athenz/zts/InstanceTestProvider.java | 17 +- .../ZTSImplPostInstanceJWTRegisterTest.java | 852 ++++++++++++++++++ .../com/yahoo/athenz/zts/ZTSImplTest.java | 395 ++++---- .../com/yahoo/athenz/zts/ZTSTestUtils.java | 70 ++ .../zts/src/test/resources/athenz-2048.conf | 16 + utils/zts-svccert/zts-svccert.go | 3 + utils/zts-svctoken/.gitignore | 1 + utils/zts-svctoken/Makefile | 58 ++ utils/zts-svctoken/README.md | 15 + utils/zts-svctoken/doc.go | 5 + utils/zts-svctoken/pom.xml | 73 ++ utils/zts-svctoken/zts-svctoken.go | 152 ++++ 28 files changed, 2683 insertions(+), 260 deletions(-) create mode 100644 libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProvider.java create mode 100644 libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProviderTest.java create mode 100644 servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplPostInstanceJWTRegisterTest.java create mode 100644 servers/zts/src/test/resources/athenz-2048.conf create mode 100644 utils/zts-svctoken/.gitignore create mode 100644 utils/zts-svctoken/Makefile create mode 100644 utils/zts-svctoken/README.md create mode 100644 utils/zts-svctoken/doc.go create mode 100644 utils/zts-svctoken/pom.xml create mode 100644 utils/zts-svctoken/zts-svctoken.go diff --git a/.gitignore b/.gitignore index f075a6dbdb1..339a0e2f679 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,9 @@ utils/zts-idtoken/src/ utils/zts-rolecert/bin/ utils/zts-rolecert/pkg/ utils/zts-rolecert/src/ +utils/zts-svctoken/bin/ +utils/zts-svctoken/pkg/ +utils/zts-svctoken/src/ utils/msd-agent/pkg/ utils/zts-accesstoken/zts-accesstoken utils/zts-rolecert/zts-rolecert diff --git a/assembly/utils/utils.xml b/assembly/utils/utils.xml index 43bf56a5261..66e80ef00ef 100644 --- a/assembly/utils/utils.xml +++ b/assembly/utils/utils.xml @@ -54,6 +54,10 @@ ${basedir}/../../utils/zts-svccert/target bin + + ${basedir}/../../utils/zts-svctoken/target + bin + ${basedir}/../../utils/zts-rolecert/target bin diff --git a/clients/go/zts/model.go b/clients/go/zts/model.go index 3860c49343a..4e2da32047e 100644 --- a/clients/go/zts/model.go +++ b/clients/go/zts/model.go @@ -2921,13 +2921,13 @@ type InstanceRegisterInformation struct { // identity attestation data including document with its signature containing // attributes like IP address, instance-id, account#, etc. // - AttestationData string `json:"attestationData"` + AttestationData string `json:"attestationData,omitempty" rdl:"optional"` // // the Certificate Signing Request for the expected X.509 certificate in the // response // - Csr string `json:"csr"` + Csr string `json:"csr,omitempty" rdl:"optional"` // // deprecated - use sshCertRequest, if present, return an SSH host @@ -2990,6 +2990,37 @@ type InstanceRegisterInformation struct { // requested ssh cert signer key id // SshCertSignerKeyId SimpleName `json:"sshCertSignerKeyId,omitempty" rdl:"optional"` + + // + // unique instance id within provider's namespace for the jwt svid + // + JwtSVIDInstanceId PathElement `json:"jwtSVIDInstanceId,omitempty" rdl:"optional"` + + // + // the audience value for the jwt svid + // + JwtSVIDAudience string `json:"jwtSVIDAudience,omitempty" rdl:"optional"` + + // + // the nonce value for the jwt svid + // + JwtSVIDNonce EntityName `json:"jwtSVIDNonce,omitempty" rdl:"optional"` + + // + // the spiffe uri for the jwt svid + // + JwtSVIDSpiffe string `json:"jwtSVIDSpiffe,omitempty" rdl:"optional"` + + // + // if true, return the spiffe uri as the jwt svid sub claim value + // + JwtSVIDSpiffeSubject *bool `json:"jwtSVIDSpiffeSubject,omitempty" rdl:"optional"` + + // + // optional signing key type - RSA or EC. Might be ignored if server doesn't + // have the requested type configured + // + JwtSVIDKeyType SimpleName `json:"jwtSVIDKeyType,omitempty" rdl:"optional"` } // NewInstanceRegisterInformation - creates an initialized InstanceRegisterInformation instance, returns a pointer to it @@ -3043,17 +3074,13 @@ func (self *InstanceRegisterInformation) Validate() error { return fmt.Errorf("InstanceRegisterInformation.service does not contain a valid SimpleName (%v)", val.Error) } } - if self.AttestationData == "" { - return fmt.Errorf("InstanceRegisterInformation.attestationData is missing but is a required field") - } else { + if self.AttestationData != "" { val := rdl.Validate(ZTSSchema(), "String", self.AttestationData) if !val.Valid { return fmt.Errorf("InstanceRegisterInformation.attestationData does not contain a valid String (%v)", val.Error) } } - if self.Csr == "" { - return fmt.Errorf("InstanceRegisterInformation.csr is missing but is a required field") - } else { + if self.Csr != "" { val := rdl.Validate(ZTSSchema(), "String", self.Csr) if !val.Valid { return fmt.Errorf("InstanceRegisterInformation.csr does not contain a valid String (%v)", val.Error) @@ -3095,6 +3122,36 @@ func (self *InstanceRegisterInformation) Validate() error { return fmt.Errorf("InstanceRegisterInformation.sshCertSignerKeyId does not contain a valid SimpleName (%v)", val.Error) } } + if self.JwtSVIDInstanceId != "" { + val := rdl.Validate(ZTSSchema(), "PathElement", self.JwtSVIDInstanceId) + if !val.Valid { + return fmt.Errorf("InstanceRegisterInformation.jwtSVIDInstanceId does not contain a valid PathElement (%v)", val.Error) + } + } + if self.JwtSVIDAudience != "" { + val := rdl.Validate(ZTSSchema(), "String", self.JwtSVIDAudience) + if !val.Valid { + return fmt.Errorf("InstanceRegisterInformation.jwtSVIDAudience does not contain a valid String (%v)", val.Error) + } + } + if self.JwtSVIDNonce != "" { + val := rdl.Validate(ZTSSchema(), "EntityName", self.JwtSVIDNonce) + if !val.Valid { + return fmt.Errorf("InstanceRegisterInformation.jwtSVIDNonce does not contain a valid EntityName (%v)", val.Error) + } + } + if self.JwtSVIDSpiffe != "" { + val := rdl.Validate(ZTSSchema(), "String", self.JwtSVIDSpiffe) + if !val.Valid { + return fmt.Errorf("InstanceRegisterInformation.jwtSVIDSpiffe does not contain a valid String (%v)", val.Error) + } + } + if self.JwtSVIDKeyType != "" { + val := rdl.Validate(ZTSSchema(), "SimpleName", self.JwtSVIDKeyType) + if !val.Valid { + return fmt.Errorf("InstanceRegisterInformation.jwtSVIDKeyType does not contain a valid SimpleName (%v)", val.Error) + } + } return nil } diff --git a/clients/go/zts/zts_schema.go b/clients/go/zts/zts_schema.go index 8b44e0ac6ca..566eaf72935 100644 --- a/clients/go/zts/zts_schema.go +++ b/clients/go/zts/zts_schema.go @@ -391,8 +391,8 @@ func init() { tInstanceRegisterInformation.Field("provider", "ServiceName", false, nil, "the provider service name (i.e. \"aws.us-west-2\", \"sys.openstack.cluster1\")") tInstanceRegisterInformation.Field("domain", "DomainName", false, nil, "the domain of the instance") tInstanceRegisterInformation.Field("service", "SimpleName", false, nil, "the service this instance is supposed to run") - tInstanceRegisterInformation.Field("attestationData", "String", false, nil, "identity attestation data including document with its signature containing attributes like IP address, instance-id, account#, etc.") - tInstanceRegisterInformation.Field("csr", "String", false, nil, "the Certificate Signing Request for the expected X.509 certificate in the response") + tInstanceRegisterInformation.Field("attestationData", "String", true, nil, "identity attestation data including document with its signature containing attributes like IP address, instance-id, account#, etc.") + tInstanceRegisterInformation.Field("csr", "String", true, nil, "the Certificate Signing Request for the expected X.509 certificate in the response") tInstanceRegisterInformation.Field("ssh", "String", true, nil, "deprecated - use sshCertRequest, if present, return an SSH host certificate. Format is JSON.") tInstanceRegisterInformation.Field("sshCertRequest", "SSHCertRequest", true, nil, "if present, return an SSH host certificate") tInstanceRegisterInformation.Field("token", "Bool", true, nil, "if true, return a service token signed by ZTS for this service") @@ -405,6 +405,12 @@ func init() { tInstanceRegisterInformation.Field("cloud", "SimpleName", true, nil, "optional cloud name where the instance is bootstrapped. e.g. aws / gcp / azure / openstack etc.") tInstanceRegisterInformation.Field("x509CertSignerKeyId", "SimpleName", true, nil, "requested x509 cert signer key id") tInstanceRegisterInformation.Field("sshCertSignerKeyId", "SimpleName", true, nil, "requested ssh cert signer key id") + tInstanceRegisterInformation.Field("jwtSVIDInstanceId", "PathElement", true, nil, "unique instance id within provider's namespace for the jwt svid") + tInstanceRegisterInformation.Field("jwtSVIDAudience", "String", true, nil, "the audience value for the jwt svid") + tInstanceRegisterInformation.Field("jwtSVIDNonce", "EntityName", true, nil, "the nonce value for the jwt svid") + tInstanceRegisterInformation.Field("jwtSVIDSpiffe", "String", true, nil, "the spiffe uri for the jwt svid") + tInstanceRegisterInformation.Field("jwtSVIDSpiffeSubject", "Bool", true, nil, "if true, return the spiffe uri as the jwt svid sub claim value") + tInstanceRegisterInformation.Field("jwtSVIDKeyType", "SimpleName", true, nil, "optional signing key type - RSA or EC. Might be ignored if server doesn't have the requested type configured") sb.AddType(tInstanceRegisterInformation.Build()) tInstanceRefreshInformation := rdl.NewStructTypeBuilder("Struct", "InstanceRefreshInformation") diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRegisterInformation.java b/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRegisterInformation.java index 5856e3f27c7..a73b4bbe35c 100644 --- a/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRegisterInformation.java +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/InstanceRegisterInformation.java @@ -16,7 +16,11 @@ public class InstanceRegisterInformation { public String provider; public String domain; public String service; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) public String attestationData; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) public String csr; @RdlOptional @JsonInclude(JsonInclude.Include.NON_EMPTY) @@ -54,6 +58,24 @@ public class InstanceRegisterInformation { @RdlOptional @JsonInclude(JsonInclude.Include.NON_EMPTY) public String sshCertSignerKeyId; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public String jwtSVIDInstanceId; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public String jwtSVIDAudience; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public String jwtSVIDNonce; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public String jwtSVIDSpiffe; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public Boolean jwtSVIDSpiffeSubject; + @RdlOptional + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public String jwtSVIDKeyType; public InstanceRegisterInformation setProvider(String provider) { this.provider = provider; @@ -174,6 +196,48 @@ public InstanceRegisterInformation setSshCertSignerKeyId(String sshCertSignerKey public String getSshCertSignerKeyId() { return sshCertSignerKeyId; } + public InstanceRegisterInformation setJwtSVIDInstanceId(String jwtSVIDInstanceId) { + this.jwtSVIDInstanceId = jwtSVIDInstanceId; + return this; + } + public String getJwtSVIDInstanceId() { + return jwtSVIDInstanceId; + } + public InstanceRegisterInformation setJwtSVIDAudience(String jwtSVIDAudience) { + this.jwtSVIDAudience = jwtSVIDAudience; + return this; + } + public String getJwtSVIDAudience() { + return jwtSVIDAudience; + } + public InstanceRegisterInformation setJwtSVIDNonce(String jwtSVIDNonce) { + this.jwtSVIDNonce = jwtSVIDNonce; + return this; + } + public String getJwtSVIDNonce() { + return jwtSVIDNonce; + } + public InstanceRegisterInformation setJwtSVIDSpiffe(String jwtSVIDSpiffe) { + this.jwtSVIDSpiffe = jwtSVIDSpiffe; + return this; + } + public String getJwtSVIDSpiffe() { + return jwtSVIDSpiffe; + } + public InstanceRegisterInformation setJwtSVIDSpiffeSubject(Boolean jwtSVIDSpiffeSubject) { + this.jwtSVIDSpiffeSubject = jwtSVIDSpiffeSubject; + return this; + } + public Boolean getJwtSVIDSpiffeSubject() { + return jwtSVIDSpiffeSubject; + } + public InstanceRegisterInformation setJwtSVIDKeyType(String jwtSVIDKeyType) { + this.jwtSVIDKeyType = jwtSVIDKeyType; + return this; + } + public String getJwtSVIDKeyType() { + return jwtSVIDKeyType; + } @Override public boolean equals(Object another) { @@ -233,6 +297,24 @@ public boolean equals(Object another) { if (sshCertSignerKeyId == null ? a.sshCertSignerKeyId != null : !sshCertSignerKeyId.equals(a.sshCertSignerKeyId)) { return false; } + if (jwtSVIDInstanceId == null ? a.jwtSVIDInstanceId != null : !jwtSVIDInstanceId.equals(a.jwtSVIDInstanceId)) { + return false; + } + if (jwtSVIDAudience == null ? a.jwtSVIDAudience != null : !jwtSVIDAudience.equals(a.jwtSVIDAudience)) { + return false; + } + if (jwtSVIDNonce == null ? a.jwtSVIDNonce != null : !jwtSVIDNonce.equals(a.jwtSVIDNonce)) { + return false; + } + if (jwtSVIDSpiffe == null ? a.jwtSVIDSpiffe != null : !jwtSVIDSpiffe.equals(a.jwtSVIDSpiffe)) { + return false; + } + if (jwtSVIDSpiffeSubject == null ? a.jwtSVIDSpiffeSubject != null : !jwtSVIDSpiffeSubject.equals(a.jwtSVIDSpiffeSubject)) { + return false; + } + if (jwtSVIDKeyType == null ? a.jwtSVIDKeyType != null : !jwtSVIDKeyType.equals(a.jwtSVIDKeyType)) { + return false; + } } return true; } diff --git a/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java b/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java index f5a8bd59b6a..ddbc9dea9dd 100644 --- a/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java +++ b/core/zts/src/main/java/com/yahoo/athenz/zts/ZTSSchema.java @@ -337,8 +337,8 @@ private static Schema build() { .field("provider", "ServiceName", false, "the provider service name (i.e. \"aws.us-west-2\", \"sys.openstack.cluster1\")") .field("domain", "DomainName", false, "the domain of the instance") .field("service", "SimpleName", false, "the service this instance is supposed to run") - .field("attestationData", "String", false, "identity attestation data including document with its signature containing attributes like IP address, instance-id, account#, etc.") - .field("csr", "String", false, "the Certificate Signing Request for the expected X.509 certificate in the response") + .field("attestationData", "String", true, "identity attestation data including document with its signature containing attributes like IP address, instance-id, account#, etc.") + .field("csr", "String", true, "the Certificate Signing Request for the expected X.509 certificate in the response") .field("ssh", "String", true, "deprecated - use sshCertRequest, if present, return an SSH host certificate. Format is JSON.") .field("sshCertRequest", "SSHCertRequest", true, "if present, return an SSH host certificate") .field("token", "Bool", true, "if true, return a service token signed by ZTS for this service") @@ -350,7 +350,13 @@ private static Schema build() { .field("namespace", "SimpleName", true, "spiffe/k8s namespace value") .field("cloud", "SimpleName", true, "optional cloud name where the instance is bootstrapped. e.g. aws / gcp / azure / openstack etc.") .field("x509CertSignerKeyId", "SimpleName", true, "requested x509 cert signer key id") - .field("sshCertSignerKeyId", "SimpleName", true, "requested ssh cert signer key id"); + .field("sshCertSignerKeyId", "SimpleName", true, "requested ssh cert signer key id") + .field("jwtSVIDInstanceId", "PathElement", true, "unique instance id within provider's namespace for the jwt svid") + .field("jwtSVIDAudience", "String", true, "the audience value for the jwt svid") + .field("jwtSVIDNonce", "EntityName", true, "the nonce value for the jwt svid") + .field("jwtSVIDSpiffe", "String", true, "the spiffe uri for the jwt svid") + .field("jwtSVIDSpiffeSubject", "Bool", true, "if true, return the spiffe uri as the jwt svid sub claim value") + .field("jwtSVIDKeyType", "SimpleName", true, "optional signing key type - RSA or EC. Might be ignored if server doesn't have the requested type configured"); sb.structType("InstanceRefreshInformation") .field("attestationData", "String", true, "identity attestation data including document with its signature containing attributes like IP address, instance-id, account#, etc.") diff --git a/core/zts/src/main/rdl/Instance.rdli b/core/zts/src/main/rdl/Instance.rdli index d22bf4b3131..47f3f307d1c 100644 --- a/core/zts/src/main/rdl/Instance.rdli +++ b/core/zts/src/main/rdl/Instance.rdli @@ -9,8 +9,8 @@ type InstanceRegisterInformation Struct { ServiceName provider; //the provider service name (i.e. "aws.us-west-2", "sys.openstack.cluster1") DomainName domain; //the domain of the instance SimpleName service; //the service this instance is supposed to run - String attestationData; //identity attestation data including document with its signature containing attributes like IP address, instance-id, account#, etc. - String csr; //the Certificate Signing Request for the expected X.509 certificate in the response + String attestationData (optional); //identity attestation data including document with its signature containing attributes like IP address, instance-id, account#, etc. + String csr (optional); //the Certificate Signing Request for the expected X.509 certificate in the response String ssh (optional); //deprecated - use sshCertRequest, if present, return an SSH host certificate. Format is JSON. SSHCertRequest sshCertRequest (optional); //if present, return an SSH host certificate Bool token (optional); //if true, return a service token signed by ZTS for this service @@ -23,6 +23,12 @@ type InstanceRegisterInformation Struct { SimpleName cloud (optional); //optional cloud name where the instance is bootstrapped. e.g. aws / gcp / azure / openstack etc. SimpleName x509CertSignerKeyId (optional); //requested x509 cert signer key id SimpleName sshCertSignerKeyId (optional); //requested ssh cert signer key id + PathElement jwtSVIDInstanceId (optional); //unique instance id within provider's namespace for the jwt svid + String jwtSVIDAudience (optional); //the audience value for the jwt svid + EntityName jwtSVIDNonce (optional); //the nonce value for the jwt svid + String jwtSVIDSpiffe (optional); //the spiffe uri for the jwt svid + Bool jwtSVIDSpiffeSubject (optional); //if true, return the spiffe uri as the jwt svid sub claim value + SimpleName jwtSVIDKeyType (optional); //optional signing key type - RSA or EC. Might be ignored if server doesn't have the requested type configured } type InstanceRefreshInformation Struct { diff --git a/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRegisterInformationTest.java b/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRegisterInformationTest.java index 768cab5d709..3f4cb3ec7de 100644 --- a/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRegisterInformationTest.java +++ b/core/zts/src/test/java/com/yahoo/athenz/zts/InstanceRegisterInformationTest.java @@ -51,6 +51,12 @@ public void testInstanceRegisterInformation() { i1.setCloud("aws"); i1.setX509CertSignerKeyId("x509KeyId"); i1.setSshCertSignerKeyId("sshKeyId"); + i1.setJwtSVIDSpiffeSubject(true); + i1.setJwtSVIDAudience("audience"); + i1.setJwtSVIDNonce("nonce"); + i1.setJwtSVIDSpiffe("spiffe://athenz.io/service"); + i1.setJwtSVIDInstanceId("instance-id"); + i1.setJwtSVIDKeyType("rsa"); i2.setProvider("provider"); i2.setAttestationData("doc"); @@ -69,6 +75,12 @@ public void testInstanceRegisterInformation() { i2.setCloud("aws"); i2.setX509CertSignerKeyId("x509KeyId"); i2.setSshCertSignerKeyId("sshKeyId"); + i2.setJwtSVIDSpiffeSubject(true); + i2.setJwtSVIDAudience("audience"); + i2.setJwtSVIDNonce("nonce"); + i2.setJwtSVIDSpiffe("spiffe://athenz.io/service"); + i2.setJwtSVIDInstanceId("instance-id"); + i2.setJwtSVIDKeyType("rsa"); // getter assertion assertEquals(i1.getAttestationData(), "doc"); @@ -88,6 +100,12 @@ public void testInstanceRegisterInformation() { assertEquals(i1.getCloud(), "aws"); assertEquals(i1.getX509CertSignerKeyId(), "x509KeyId"); assertEquals(i1.getSshCertSignerKeyId(), "sshKeyId"); + assertTrue(i1.getJwtSVIDSpiffeSubject()); + assertEquals(i1.getJwtSVIDAudience(), "audience"); + assertEquals(i1.getJwtSVIDNonce(), "nonce"); + assertEquals(i1.getJwtSVIDSpiffe(), "spiffe://athenz.io/service"); + assertEquals(i1.getJwtSVIDInstanceId(), "instance-id"); + assertEquals(i1.getJwtSVIDKeyType(), "rsa"); assertEquals(i2, i1); assertEquals(i2, i2); @@ -213,5 +231,47 @@ public void testInstanceRegisterInformation() { assertNotEquals(i1, i2); i2.setSshCertSignerKeyId("sshKeyId"); assertEquals(i1, i2); + + i2.setJwtSVIDSpiffeSubject(null); + assertNotEquals(i1, i2); + i2.setJwtSVIDSpiffeSubject(false); + assertNotEquals(i1, i2); + i2.setJwtSVIDSpiffeSubject(true); + assertEquals(i1, i2); + + i2.setJwtSVIDAudience(null); + assertNotEquals(i1, i2); + i2.setJwtSVIDAudience("audience2"); + assertNotEquals(i1, i2); + i2.setJwtSVIDAudience("audience"); + assertEquals(i1, i2); + + i2.setJwtSVIDNonce(null); + assertNotEquals(i1, i2); + i2.setJwtSVIDNonce("nonce2"); + assertNotEquals(i1, i2); + i2.setJwtSVIDNonce("nonce"); + assertEquals(i1, i2); + + i2.setJwtSVIDSpiffe(null); + assertNotEquals(i1, i2); + i2.setJwtSVIDSpiffe("spiffe://athenz.io/service2"); + assertNotEquals(i1, i2); + i2.setJwtSVIDSpiffe("spiffe://athenz.io/service"); + assertEquals(i1, i2); + + i2.setJwtSVIDKeyType(null); + assertNotEquals(i1, i2); + i2.setJwtSVIDKeyType("ec"); + assertNotEquals(i1, i2); + i2.setJwtSVIDKeyType("rsa"); + assertEquals(i1, i2); + + i2.setJwtSVIDInstanceId(null); + assertNotEquals(i1, i2); + i2.setJwtSVIDInstanceId("instance-id-2"); + assertNotEquals(i1, i2); + i2.setJwtSVIDInstanceId("instance-id"); + assertEquals(i1, i2); } } diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/IdToken.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/IdToken.java index 5c958a9aeb0..ac78c4c9fe3 100644 --- a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/IdToken.java +++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/token/IdToken.java @@ -38,9 +38,11 @@ public class IdToken extends OAuth2Token { public static final String CLAIM_GROUPS = "groups"; public static final String CLAIM_NONCE = "nonce"; + public static final String CLAIM_SPIFFE = "spiffe"; private List groups; private String nonce; + private String spiffe; /** * Creates an empty id token @@ -86,6 +88,7 @@ public IdToken(final String token, ConfigurableJWTProcessor jwt void setIdTokenFields() { setNonce(JwtsHelper.getStringClaim(claimsSet, CLAIM_NONCE)); setGroups(JwtsHelper.getStringListClaim(claimsSet, CLAIM_GROUPS)); + setSpiffe(JwtsHelper.getStringClaim(claimsSet, CLAIM_SPIFFE)); } public List getGroups() { @@ -104,6 +107,14 @@ public void setNonce(String nonce) { this.nonce = nonce; } + public String getSpiffe() { + return spiffe; + } + + public void setSpiffe(String spiffe) { + this.spiffe = spiffe; + } + public String getSignedToken(final PrivateKey key, final String keyId, final String sigAlg) { try { @@ -117,7 +128,8 @@ public String getSignedToken(final PrivateKey key, final String keyId, final Str .claim(CLAIM_AUTH_TIME, authTime) .claim(CLAIM_VERSION, version) .claim(CLAIM_GROUPS, groups) - .claim(CLAIM_NONCE, nonce); + .claim(CLAIM_NONCE, nonce) + .claim(CLAIM_SPIFFE, spiffe); if (customClaims != null) { for (Map.Entry entry : customClaims.entrySet()) { claimsSetBuilder.claim(entry.getKey(), entry.getValue()); @@ -145,6 +157,7 @@ public boolean isStandardClaim(final String claimName) { switch (claimName) { case CLAIM_GROUPS: case CLAIM_NONCE: + case CLAIM_SPIFFE: return true; default: return false; diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/IdTokenTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/IdTokenTest.java index 1fffe3ac1dd..66735619d10 100644 --- a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/IdTokenTest.java +++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/token/IdTokenTest.java @@ -108,6 +108,40 @@ public void testIdToken() throws JOSEException, ParseException { assertEquals(claimsSet.getIssuer(), "athenz"); } + @Test + public void testIdTokenWithSpiffe() throws JOSEException, ParseException { + + long now = System.currentTimeMillis() / 1000; + + IdToken token = createIdToken(now); + token.setSpiffe("spiffe://athenz.io/dev"); + + // verify the getters + + validateIdToken(token, now); + assertEquals(token.getSpiffe(), "spiffe://athenz.io/dev"); + + // now get the signed token + + PrivateKey privateKey = Crypto.loadPrivateKey(ecPrivateKey); + String idJws = token.getSignedToken(privateKey, "eckey1", "ES256"); + assertNotNull(idJws); + + // now verify our signed token + + PublicKey publicKey = Crypto.loadPublicKey(ecPublicKey); + JWSVerifier verifier = new ECDSAVerifier((ECPublicKey) publicKey); + SignedJWT signedJWT = SignedJWT.parse(idJws); + assertTrue(signedJWT.verify(verifier)); + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + assertNotNull(claimsSet); + + assertEquals(claimsSet.getSubject(), "subject"); + assertEquals(claimsSet.getAudience().get(0), "coretech"); + assertEquals(claimsSet.getIssuer(), "athenz"); + assertEquals(claimsSet.getStringClaim("spiffe"), "spiffe://athenz.io/dev"); + } + @Test public void testIdTokenCustomClaims() throws JOSEException, ParseException { diff --git a/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/InstanceProvider.java b/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/InstanceProvider.java index 9bb48a81bb5..3930f9bc7db 100644 --- a/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/InstanceProvider.java +++ b/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/InstanceProvider.java @@ -87,15 +87,29 @@ enum Scheme { UNKNOWN } + enum SVIDType { + X509, + JWT + } + /** * Get Provider scheme. Currently supported schemes are HTTP - * or CLASS. By default we'll return UNKNOWN. + * or CLASS. By default, we'll return UNKNOWN. * @return the scheme for the provider */ default Scheme getProviderScheme() { return Scheme.UNKNOWN; } + /** + * Get the type of SVID issued by this provider. Currently supported + * types are X509 or JWT. By default, we'll return X509. + * @return the SVID type for the provider + */ + default SVIDType getSVIDType() { + return SVIDType.X509; + } + /** * Set provider details and initialize the provider object * @param provider name of the provider (service identity name) diff --git a/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProvider.java b/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProvider.java new file mode 100644 index 00000000000..0538cc25930 --- /dev/null +++ b/libs/java/instance_provider/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProvider.java @@ -0,0 +1,168 @@ +/* + * 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.instance.provider.impl; + +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.auth.util.Crypto; +import com.yahoo.athenz.instance.provider.InstanceConfirmation; +import com.yahoo.athenz.instance.provider.InstanceProvider; +import com.yahoo.athenz.instance.provider.ProviderResourceException; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.eclipse.jetty.util.StringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLContext; +import javax.security.auth.x500.X500Principal; +import java.util.*; +import java.util.stream.Collectors; + +public class InstanceAthenzRBACProvider implements InstanceProvider { + + private static final Logger LOGGER = LoggerFactory.getLogger(InstanceAthenzRBACProvider.class); + + static final String ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST = "athenz.zts.athenz_rbac_provider_issuer_dn_list"; + static final String ATHENZ_RBAC_ACTION = "zts.assume_service"; + + String provider = null; + Authorizer authorizer = null; + Set issuerDNs = null; + + @Override + public Scheme getProviderScheme() { + return Scheme.CLASS; + } + + @Override + public SVIDType getSVIDType() { + return SVIDType.JWT; + } + + @Override + public void initialize(String provider, String providerEndpoint, SSLContext sslContext, + KeyStore keyStore) { + + // save our provider name + + this.provider = provider; + + final String issuerList = System.getProperty(ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST); + if (!StringUtil.isEmpty(issuerList)) { + issuerDNs = parseDnList(Arrays.asList(issuerList.split(";"))); + } + } + + Set parseDnList(List list) { + return list.stream() + .map(dn -> new X500Principal(dn).getName()) + .collect(Collectors.toSet()); + } + + ProviderResourceException forbiddenError(String message) { + LOGGER.error(message); + return new ProviderResourceException(ProviderResourceException.FORBIDDEN, message); + } + + @Override + public void setAuthorizer(Authorizer authorizer) { + this.authorizer = authorizer; + } + + @Override + public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) throws ProviderResourceException { + + // before running any checks make sure we have a valid authorizer + + if (authorizer == null) { + throw forbiddenError("Authorizer not available"); + } + + final String domainName = confirmation.getDomain(); + final String serviceName = confirmation.getService(); + final Map instanceAttributes = confirmation.getAttributes(); + + // validate that the request is from a known issuer + + if (!validateIssuer(instanceAttributes)) { + throw forbiddenError("Invalid issuer DN"); + } + + // extract the certificate dn and extract the cn + + final String dn = InstanceUtils.getInstanceProperty(instanceAttributes, InstanceProvider.ZTS_INSTANCE_CERT_SUBJECT_DN); + if (StringUtil.isEmpty(dn)) { + throw forbiddenError("No certificate subject DN provided"); + } + + final String clientIdentity = Crypto.extractX500DnField(dn, BCStyle.CN); + if (StringUtil.isEmpty(clientIdentity)) { + throw forbiddenError("Unable to extract certificate subject CN from DN: " + dn); + } + int index = clientIdentity.lastIndexOf('.'); + if (index == -1) { + throw forbiddenError("Invalid certificate subject CN: " + clientIdentity); + } + final String clientDomain = clientIdentity.substring(0, index); + final String clientService = clientIdentity.substring(index + 1); + if (clientDomain.isEmpty() || clientService.isEmpty()) { + throw forbiddenError("Invalid certificate subject CN: " + clientIdentity); + } + + // carry out our authorization check to see if the given principal + // is authorized to assume the given service identity + + final String resource = domainName + ":service." + serviceName; + Principal principal = SimplePrincipal.create(clientDomain, clientService, (String) null); + boolean accessCheck = authorizer.access(ATHENZ_RBAC_ACTION, resource, principal, null); + if (!accessCheck) { + throw forbiddenError("Service: " + clientIdentity + " not authorized to assume identity: " + + domainName + "." + serviceName); + } + + return confirmation; + } + + @Override + public InstanceConfirmation refreshInstance(InstanceConfirmation confirmation) throws ProviderResourceException { + throw forbiddenError("JWT SVIDs cannot be refreshed"); + } + + /** + * validateIssuer ensures that IssuerDN is passed in. + * If issuerDNs is configured, it validates the IssuerDN against the configured values + * @param attributes map of attributes passed by ZTS + * @return true if the issuer dn is in our configured list + */ + boolean validateIssuer(final Map attributes) { + + final String dn = InstanceUtils.getInstanceProperty(attributes, InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN); + if (StringUtil.isEmpty(dn)) { + LOGGER.error("issuer DN must be passed by ZTS"); + return false; + } + + // If no allow list configured, accept any issuer + + if (issuerDNs == null) { + return true; + } + + return issuerDNs.contains(dn); + } +} diff --git a/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/InstanceProviderTest.java b/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/InstanceProviderTest.java index 6a10e2fb118..2896f939bd4 100644 --- a/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/InstanceProviderTest.java +++ b/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/InstanceProviderTest.java @@ -52,6 +52,7 @@ public InstanceConfirmation refreshInstance(InstanceConfirmation confirmation) { provider.setPubKeysProvider(null); assertEquals(provider.getProviderScheme(), InstanceProvider.Scheme.UNKNOWN); + assertEquals(provider.getSVIDType(), InstanceProvider.SVIDType.X509); try { provider.getInstanceRegisterToken(null); diff --git a/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProviderTest.java b/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProviderTest.java new file mode 100644 index 00000000000..c080d5fff66 --- /dev/null +++ b/libs/java/instance_provider/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceAthenzRBACProviderTest.java @@ -0,0 +1,529 @@ +/* + * 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 "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.instance.provider.impl; + +import java.util.*; + +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; +import org.mockito.Mockito; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.*; +import static org.testng.Assert.*; + +public class InstanceAthenzRBACProviderTest { + + private static final String TEST_ISSUER_DN = "CN=Test Issuer,OU=Test,O=Athenz"; + private static final String TEST_SUBJECT_DN = "CN=athenz.service,OU=Test,O=Athenz"; + + private InstanceAthenzRBACProvider provider; + + @BeforeMethod + public void setUp() { + provider = new InstanceAthenzRBACProvider(); + System.clearProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST); + } + + @AfterMethod + public void tearDown() { + System.clearProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST); + } + + @Test + public void testGetProviderScheme() { + assertEquals(provider.getProviderScheme(), InstanceProvider.Scheme.CLASS); + } + + @Test + public void testInitializeWithoutIssuerList() { + provider.initialize("test-provider", "class://test", null, null); + assertNull(provider.issuerDNs); + assertEquals(provider.provider, "test-provider"); + assertEquals(provider.getSVIDType(), InstanceProvider.SVIDType.JWT); + } + + @Test + public void testInitializeWithIssuerList() { + System.setProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST, + TEST_ISSUER_DN + ";CN=Another Issuer,OU=Test"); + provider.initialize("test-provider", "class://test", null, null); + assertNotNull(provider.issuerDNs); + assertEquals(provider.issuerDNs.size(), 2); + assertTrue(provider.issuerDNs.contains(TEST_ISSUER_DN)); + assertEquals(provider.provider, "test-provider"); + } + + @Test + public void testInitializeWithEmptyIssuerList() { + System.setProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST, ""); + provider.initialize("test-provider", "class://test", null, null); + assertNull(provider.issuerDNs); + } + + @Test + public void testSetAuthorizer() { + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + assertEquals(provider.authorizer, authorizer); + } + + @Test + public void testConfirmInstanceWithoutAuthorizer() { + provider.initialize("test-provider", "class://test", null, null); + InstanceConfirmation confirmation = createBasicConfirmation("athenz", "service", + TEST_ISSUER_DN, TEST_SUBJECT_DN); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("Authorizer not available")); + } + } + + @Test + public void testConfirmInstanceWithoutIssuerDN() { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + InstanceConfirmation confirmation = createBasicConfirmation("athenz", "service", + null, TEST_SUBJECT_DN); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("Invalid issuer DN")); + } + } + + @Test + public void testConfirmInstanceWithIssuerDNNotInList() { + System.setProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST, + "CN=Other Issuer,OU=Test"); + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + InstanceConfirmation confirmation = createBasicConfirmation("athenz", "service", + TEST_ISSUER_DN, TEST_SUBJECT_DN); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("Invalid issuer DN")); + } + } + + @Test + public void testConfirmInstanceWithoutSubjectDN() { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + InstanceConfirmation confirmation = createBasicConfirmation("athenz", "service", + TEST_ISSUER_DN, null); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("No certificate subject DN provided")); + } + } + + @Test + public void testConfirmInstanceWithEmptySubjectDN() { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + InstanceConfirmation confirmation = createBasicConfirmation("athenz", "service", + TEST_ISSUER_DN, ""); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("No certificate subject DN provided")); + } + } + + @Test + public void testConfirmInstanceWithInvalidCNFormat() { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String invalidSubjectDN = "CN=invalid-cn-without-dot,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("athenz", "service", + TEST_ISSUER_DN, invalidSubjectDN); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("Invalid certificate subject CN")); + } + } + + @Test + public void testConfirmInstanceWithSubjectDNMissingCN() { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDNWithoutCN = "OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("athenz", "service", + TEST_ISSUER_DN, subjectDNWithoutCN); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("Unable to extract certificate subject CN")); + } + } + + @Test + public void testConfirmInstanceWithAuthorizationFailure() { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=athenz.service,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("sports", "api", + TEST_ISSUER_DN, subjectDN); + + Principal principal = SimplePrincipal.create("athenz", "service", (String) null); + String resource = "sports:service.api"; + Mockito.when(authorizer.access(eq(InstanceAthenzRBACProvider.ATHENZ_RBAC_ACTION), + eq(resource), eq(principal), isNull())).thenReturn(false); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("not authorized to assume identity")); + assertTrue(ex.getMessage().contains("athenz.service")); + assertTrue(ex.getMessage().contains("sports.api")); + } + } + + @Test + public void testConfirmInstanceSuccess() throws ProviderResourceException { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=athenz.service,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("sports", "api", + TEST_ISSUER_DN, subjectDN); + + Principal principal = SimplePrincipal.create("athenz", "service", (String) null); + String resource = "sports:service.api"; + Mockito.when(authorizer.access(eq(InstanceAthenzRBACProvider.ATHENZ_RBAC_ACTION), + eq(resource), eq(principal), isNull())).thenReturn(true); + + InstanceConfirmation result = provider.confirmInstance(confirmation); + assertNotNull(result); + assertEquals(result.getDomain(), "sports"); + assertEquals(result.getService(), "api"); + } + + @Test + public void testConfirmInstanceSuccessWithIssuerDNInList() throws ProviderResourceException { + System.setProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST, + TEST_ISSUER_DN + ";CN=Another Issuer,OU=Test"); + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=athenz.service,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("sports", "api", + TEST_ISSUER_DN, subjectDN); + + Principal principal = SimplePrincipal.create("athenz", "service", (String) null); + String resource = "sports:service.api"; + Mockito.when(authorizer.access(eq(InstanceAthenzRBACProvider.ATHENZ_RBAC_ACTION), + eq(resource), eq(principal), isNull())).thenReturn(true); + + InstanceConfirmation result = provider.confirmInstance(confirmation); + assertNotNull(result); + } + + @Test + public void testConfirmInstanceSuccessWithoutIssuerDNList() throws ProviderResourceException { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=athenz.service,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("sports", "api", + TEST_ISSUER_DN, subjectDN); + + Principal principal = SimplePrincipal.create("athenz", "service", (String) null); + String resource = "sports:service.api"; + Mockito.when(authorizer.access(eq(InstanceAthenzRBACProvider.ATHENZ_RBAC_ACTION), + eq(resource), eq(principal), isNull())).thenReturn(true); + + InstanceConfirmation result = provider.confirmInstance(confirmation); + assertNotNull(result); + } + + @Test + public void testConfirmInstanceWithComplexDomainAndService() throws ProviderResourceException { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=coretech.weather,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("sports", "api", + TEST_ISSUER_DN, subjectDN); + + Principal principal = SimplePrincipal.create("coretech", "weather", (String) null); + String resource = "sports:service.api"; + Mockito.when(authorizer.access(eq(InstanceAthenzRBACProvider.ATHENZ_RBAC_ACTION), + eq(resource), eq(principal), isNull())).thenReturn(true); + + InstanceConfirmation result = provider.confirmInstance(confirmation); + assertNotNull(result); + } + + @Test + public void testRefreshInstance() { + provider.initialize("test-provider", "class://test", null, null); + InstanceConfirmation confirmation = new InstanceConfirmation(); + + try { + provider.refreshInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("JWT SVIDs cannot be refreshed")); + } + } + + @Test + public void testValidateIssuerWithoutIssuerDN() { + provider.initialize("test-provider", "class://test", null, null); + Map attributes = new HashMap<>(); + + boolean result = provider.validateIssuer(attributes); + assertFalse(result); + } + + @Test + public void testValidateIssuerWithEmptyIssuerDN() { + provider.initialize("test-provider", "class://test", null, null); + Map attributes = new HashMap<>(); + attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN, ""); + + boolean result = provider.validateIssuer(attributes); + assertFalse(result); + } + + @Test + public void testValidateIssuerWithoutIssuerDNList() { + provider.initialize("test-provider", "class://test", null, null); + Map attributes = new HashMap<>(); + attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN, TEST_ISSUER_DN); + + boolean result = provider.validateIssuer(attributes); + assertTrue(result); + } + + @Test + public void testValidateIssuerWithIssuerDNInList() { + System.setProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST, + TEST_ISSUER_DN + ";CN=Another Issuer,OU=Test"); + provider.initialize("test-provider", "class://test", null, null); + Map attributes = new HashMap<>(); + attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN, TEST_ISSUER_DN); + + boolean result = provider.validateIssuer(attributes); + assertTrue(result); + } + + @Test + public void testValidateIssuerWithIssuerDNNotInList() { + System.setProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST, + "CN=Other Issuer,OU=Test"); + provider.initialize("test-provider", "class://test", null, null); + Map attributes = new HashMap<>(); + attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN, TEST_ISSUER_DN); + + boolean result = provider.validateIssuer(attributes); + assertFalse(result); + } + + @Test + public void testValidateIssuerWithMultipleIssuersInList() { + String issuer1 = "CN=Issuer1,OU=Test"; + String issuer2 = "CN=Issuer2,OU=Test"; + System.setProperty(InstanceAthenzRBACProvider.ZTS_PROP_ATHENZ_RBAC_ISSUER_DN_LIST, + issuer1 + ";" + issuer2); + provider.initialize("test-provider", "class://test", null, null); + + Map attributes1 = new HashMap<>(); + attributes1.put(InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN, issuer1); + assertTrue(provider.validateIssuer(attributes1)); + + Map attributes2 = new HashMap<>(); + attributes2.put(InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN, issuer2); + assertTrue(provider.validateIssuer(attributes2)); + } + + @Test + public void testParseDnList() { + provider.initialize("test-provider", "class://test", null, null); + List dnList = Arrays.asList( + "CN=Test Issuer,OU=Test,O=Athenz", + "CN=Another Issuer,OU=Test,O=Athenz" + ); + + Set result = provider.parseDnList(dnList); + assertNotNull(result); + assertEquals(result.size(), 2); + assertTrue(result.contains("CN=Test Issuer,OU=Test,O=Athenz")); + assertTrue(result.contains("CN=Another Issuer,OU=Test,O=Athenz")); + } + + @Test + public void testParseDnListWithEmptyList() { + provider.initialize("test-provider", "class://test", null, null); + List dnList = List.of(); + + Set result = provider.parseDnList(dnList); + assertNotNull(result); + assertEquals(result.size(), 0); + } + + @Test + public void testParseDnListWithSingleDN() { + provider.initialize("test-provider", "class://test", null, null); + List dnList = List.of("CN=Single Issuer,OU=Test"); + + Set result = provider.parseDnList(dnList); + assertNotNull(result); + assertEquals(result.size(), 1); + assertTrue(result.contains("CN=Single Issuer,OU=Test")); + } + + @Test + public void testForbiddenError() { + provider.initialize("test-provider", "class://test", null, null); + String errorMessage = "Test error message"; + + ProviderResourceException ex = provider.forbiddenError(errorMessage); + assertNotNull(ex); + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains(errorMessage)); + } + + @Test + public void testConfirmInstanceWithCNAtStart() throws ProviderResourceException { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=test.service,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("domain", "svc", + TEST_ISSUER_DN, subjectDN); + + Principal principal = SimplePrincipal.create("test", "service", (String) null); + String resource = "domain:service.svc"; + Mockito.when(authorizer.access(eq(InstanceAthenzRBACProvider.ATHENZ_RBAC_ACTION), + eq(resource), eq(principal), isNull())).thenReturn(true); + + InstanceConfirmation result = provider.confirmInstance(confirmation); + assertNotNull(result); + } + + @Test + public void testConfirmInstanceWithCNContainingMultipleDots() throws ProviderResourceException { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=sub.domain.service,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("target", "api", + TEST_ISSUER_DN, subjectDN); + + Principal principal = SimplePrincipal.create("sub.domain", "service", (String) null); + String resource = "target:service.api"; + Mockito.when(authorizer.access(eq(InstanceAthenzRBACProvider.ATHENZ_RBAC_ACTION), + eq(resource), eq(principal), isNull())).thenReturn(true); + + InstanceConfirmation result = provider.confirmInstance(confirmation); + assertNotNull(result); + } + + @Test + public void testConfirmInstanceWithCNLastDotAtEnd() { + provider.initialize("test-provider", "class://test", null, null); + Authorizer authorizer = Mockito.mock(Authorizer.class); + provider.setAuthorizer(authorizer); + + String subjectDN = "CN=domain.,OU=Test,O=Athenz"; + InstanceConfirmation confirmation = createBasicConfirmation("target", "api", + TEST_ISSUER_DN, subjectDN); + + try { + provider.confirmInstance(confirmation); + fail("Should have thrown ProviderResourceException"); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("Invalid certificate subject CN")); + } + } + + private InstanceConfirmation createBasicConfirmation(String domain, String service, + String issuerDN, String subjectDN) { + InstanceConfirmation confirmation = new InstanceConfirmation(); + confirmation.setDomain(domain); + confirmation.setService(service); + + Map attributes = new HashMap<>(); + if (issuerDN != null) { + attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_ISSUER_DN, issuerDN); + } + if (subjectDN != null) { + attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_SUBJECT_DN, subjectDN); + } + confirmation.setAttributes(attributes); + + return confirmation; + } +} + diff --git a/pom.xml b/pom.xml index 279d0f033bc..cfc2bc32d96 100644 --- a/pom.xml +++ b/pom.xml @@ -218,6 +218,7 @@ utils/zts-idtoken utils/zts-rolecert utils/zts-svccert + utils/zts-svctoken utils/msd-agent libs/nodejs/auth_core clients/nodejs/zts diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java index 0b30f18489c..0410578e3fa 100644 --- a/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSImpl.java @@ -4484,6 +4484,26 @@ public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceReg throw forbiddenError(errorMsg.toString(), caller, domain, principalDomain); } + if (StringUtil.isEmpty(info.getCsr())) { + return postInstanceJWTRegister(ctx, info, domain, service, cn, principalDomain, provider, caller); + } else { + return postInstanceX509CertificateRegister(ctx, info, domain, service, cn, principalDomain, + domainData, provider, errorMsg, serviceIdentity, ipAddress, caller); + } + } + + Response postInstanceX509CertificateRegister(ResourceContext ctx, InstanceRegisterInformation info, + final String domain, final String service, final String cn, final String principalDomain, + DomainData domainData, final String provider, StringBuilder errorMsg, + com.yahoo.athenz.zms.ServiceIdentity serviceIdentity, final String ipAddress, final String caller) { + + // make sure we have valid attestation data for our x.509 request + + if (StringUtil.isEmpty(info.getAttestationData())) { + throw requestError("attestation data is required for x509 certificate request", + caller, domain, principalDomain); + } + // validate request/csr details X509ServiceCertRequest certReq; @@ -4506,13 +4526,11 @@ public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceReg final String certReqInstanceId = certReq.getInstanceId(); - // validate attestation data is included in the request + // get our instance provider, the method will throw an exception + // if the provider is not found or invalid type - InstanceProvider instanceProvider = instanceProviderManager.getProvider(provider, hostnameResolver); - if (instanceProvider == null) { - throw requestError("unable to get instance for provider: " + provider, - caller, domain, principalDomain); - } + InstanceProvider instanceProvider = getInstanceProvider(provider, InstanceProvider.SVIDType.X509, + domain, principalDomain, caller); // include instance details in the query access log to help // with debugging requests @@ -4531,28 +4549,10 @@ public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceReg InstanceProvider.ZTS_INSTANCE_SAN_IP); // make sure to close our provider when its no longer needed + // and the method will do that for us - Object timerProviderMetric = metric.startTiming("providerregister_timing", provider, null, - null, null, Metric.TimerMetricType.PROVIDER_LATENCY); - int providerStatusCode = ResourceException.OK; - try { - instance = instanceProvider.confirmInstance(instance); - } catch (ProviderResourceException ex) { - metric.increment("providerconfirm_failure", domain, provider); - providerStatusCode = (ex.getCode() == ProviderResourceException.GATEWAY_TIMEOUT) ? - ResourceException.GATEWAY_TIMEOUT : ResourceException.FORBIDDEN; - throw error(providerStatusCode, getExceptionMsg("unable to verify attestation data: ", ctx, - ex, info.getHostname()), caller, domain, principalDomain); - } catch (Exception ex) { - metric.increment("providerconfirm_failure", domain, provider); - providerStatusCode = ResourceException.FORBIDDEN; - throw forbiddenError(getExceptionMsg("unable to verify attestation data: ", ctx, ex, info.getHostname()), - caller, domain, principalDomain); - } finally { - metric.stopTiming(timerProviderMetric, provider, null, null, providerStatusCode, null); - closeInstanceProvider(instanceProvider); - } - metric.increment("providerconfirm_success", domain, provider); + instance = validateConfirmationData(ctx, instance, instanceProvider, provider, domain, + principalDomain, info.getHostname(), caller); // determine what type of certificate the provider is authorizing // this instance to get - possible values are: server, client or @@ -4560,8 +4560,8 @@ public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceReg // going to see if the provider wants to impose an expiry time // though the certificate signer might decide to ignore that // request and override it with its own value. Other optional - // attributes we get back from the provider include whether or - // not the certs can be refreshed or ssh certs can be requested + // attributes we get back from the provider include whether + // the certs can be refreshed or ssh certs can be requested String certUsage = null; String certSubjectOU = null; @@ -4617,7 +4617,7 @@ public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceReg // generate an ssh object for recording Set attestedSshCertPrincipalSet = createSshPrincipalsSet(attestedSshCertPrincipals, - instancePrivateIp, ipAddress); + instancePrivateIp, ipAddress); SSHCertRecord certRecord = generateSSHCertRecord(ctx, cn, certReqInstanceId, instancePrivateIp); instanceCertManager.generateSSHIdentity(null, identity, info.getHostname(), info.getSsh(), info.getSshCertRequest(), certRecord, ZTSConsts.ZTS_SSH_HOST, false, attestedSshCertPrincipalSet, @@ -4662,8 +4662,8 @@ public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceReg if (info.getToken() == Boolean.TRUE) { ServerPrivateKey privateKey = getServerPrivateKey(keyAlgoForProprietaryObjects); PrincipalToken svcToken = new PrincipalToken.Builder("S1", domain, service) - .expirationWindow(svcTokenTimeout).keyId(privateKey.getId()).host(serverHostName) - .ip(ipAddress).keyService(ZTSConsts.ZTS_SERVICE).build(); + .expirationWindow(svcTokenTimeout).keyId(privateKey.getId()).host(serverHostName) + .ip(ipAddress).keyService(ZTSConsts.ZTS_SERVICE).build(); svcToken.sign(privateKey.getKey()); identity.setServiceToken(svcToken.getSignedToken()); } @@ -4680,6 +4680,146 @@ public Response postInstanceRegisterInformation(ResourceContext ctx, InstanceReg .header("Location", location).build(); } + Response postInstanceJWTRegister(ResourceContext ctx, InstanceRegisterInformation info, + final String domain, final String service, final String cn, final String principalDomain, + final String provider, final String caller) { + + // we need to validate our spiffe value if one is provided + + final String spiffeUri = info.getJwtSVIDSpiffe(); + if (!StringUtil.isEmpty(spiffeUri)) { + if (!spiffeUriManager.validateServiceCertUri(spiffeUri, domain, service, info.getNamespace())) { + throw requestError("SPIFFE URI validation failed", caller, domain, principalDomain); + } + } else if (info.getJwtSVIDSpiffeSubject() == Boolean.TRUE) { + throw requestError("SPIFFE URI is required when jwtSVIDSpiffeSubject is true", + caller, domain, principalDomain); + } + + // get our instance provider, the method will throw an exception + // if the provider is not found or invalid type + + InstanceProvider instanceProvider = getInstanceProvider(provider, InstanceProvider.SVIDType.JWT, + domain, principalDomain, caller); + + // include instance details in the query access log to help + // with debugging requests + + final String jwtReqInstanceId = info.getJwtSVIDInstanceId(); + + ctx.request().setAttribute(ACCESS_LOG_ADDL_QUERY, + getInstanceRegisterQueryLog(provider, jwtReqInstanceId, info.getHostname())); + + InstanceConfirmation instance = newInstanceConfirmationForRegister(ctx, provider, domain, + service, info.getAttestationData(), jwtReqInstanceId, info.getHostname(), + null, instanceProvider.getProviderScheme(), info.getCloud()); + + // make sure to close our provider when its no longer needed + // and the method will do that for us + + validateConfirmationData(ctx, instance, instanceProvider, provider, domain, + principalDomain, info.getHostname(), caller); + + // set the required attributes in the identity object + + InstanceIdentity identity = new InstanceIdentity(); + identity.setName(cn); + identity.setProvider(provider); + identity.setInstanceId(jwtReqInstanceId); + + // generate id token for the instance + + long iat = System.currentTimeMillis() / 1000; + + IdToken idToken = new IdToken(); + idToken.setVersion(1); + idToken.setAudience(info.getJwtSVIDAudience()); + idToken.setIssuer(ztsOpenIDIssuer); + idToken.setNonce(info.getJwtSVIDNonce()); + idToken.setIssueTime(iat); + idToken.setAuthTime(iat); + + if (info.getJwtSVIDSpiffeSubject() == Boolean.TRUE) { + idToken.setSubject(info.getJwtSVIDSpiffe()); + } else { + idToken.setSubject(cn); + if (!StringUtil.isEmpty(info.getJwtSVIDSpiffe())) { + idToken.setSpiffe(info.getJwtSVIDSpiffe()); + } + } + + // for user principals we're going to use the default 1 hour while for + // service principals 12 hours as the max timeout, unless the client + // is explicitly asking for something smaller. + + long expiryTime = iat + determineOIDCIdTokenTimeout(principalDomain, info.getExpiryTime()); + idToken.setExpiryTime(expiryTime); + + ServerPrivateKey signPrivateKey = getSignPrivateKey(info.getJwtSVIDKeyType()); + identity.setServiceToken(idToken.getSignedToken(signPrivateKey.getKey(), + signPrivateKey.getId(), signPrivateKey.getAlgorithm())); + + final String location = "/zts/v1/instance/" + provider + "/" + domain + + "/" + service + "/" + jwtReqInstanceId; + return Response.status(ResourceException.CREATED).entity(identity) + .header("Location", location).build(); + } + + InstanceConfirmation validateConfirmationData(ResourceContext ctx, InstanceConfirmation instance, + InstanceProvider instanceProvider, final String provider, final String domain, + final String principalDomain, final String hostname, final String caller) { + + // make sure to close our provider when its no longer needed + + Object timerProviderMetric = metric.startTiming("providerregister_timing", provider, null, + null, null, Metric.TimerMetricType.PROVIDER_LATENCY); + int providerStatusCode = ResourceException.OK; + + try { + + instance = instanceProvider.confirmInstance(instance); + + } catch (ProviderResourceException ex) { + + metric.increment("providerconfirm_failure", domain, provider); + providerStatusCode = (ex.getCode() == ProviderResourceException.GATEWAY_TIMEOUT) ? + ResourceException.GATEWAY_TIMEOUT : ResourceException.FORBIDDEN; + throw error(providerStatusCode, getExceptionMsg("unable to verify attestation data: ", ctx, + ex, hostname), caller, domain, principalDomain); + + } catch (Exception ex) { + + metric.increment("providerconfirm_failure", domain, provider); + providerStatusCode = ResourceException.FORBIDDEN; + throw forbiddenError(getExceptionMsg("unable to verify attestation data: ", ctx, ex, hostname), + caller, domain, principalDomain); + + } finally { + + metric.stopTiming(timerProviderMetric, provider, null, null, providerStatusCode, null); + closeInstanceProvider(instanceProvider); + } + + metric.increment("providerconfirm_success", domain, provider); + return instance; + } + + InstanceProvider getInstanceProvider(final String providerName, InstanceProvider.SVIDType providerType, + final String domainName, final String principalDomain, final String caller) { + + InstanceProvider instanceProvider = instanceProviderManager.getProvider(providerName, hostnameResolver); + if (instanceProvider == null) { + throw requestError("unable to get instance for provider: " + providerName, + caller, domainName, principalDomain); + } + + if (instanceProvider.getSVIDType() != providerType) { + throw requestError("invalid instance provider type for " + providerType + ": " + providerName, + caller, domainName, principalDomain); + } + return instanceProvider; + } + String getServiceX509KeySignerId(DomainData domainData, com.yahoo.athenz.zms.ServiceIdentity serviceIdentity, final String requestSignerKeyId) { String signerKeyId; @@ -4821,25 +4961,7 @@ InstanceConfirmation generateInstanceConfirmObject(ResourceContext ctx, final St Map attributes = new HashMap<>(); attributes.put(InstanceProvider.ZTS_INSTANCE_ID, instanceId); - attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_DNS, String.join(",", certReq.getProviderDnsNames())); attributes.put(InstanceProvider.ZTS_INSTANCE_CLIENT_IP, ServletRequestUtil.getRemoteAddress(ctx.request())); - final List certReqIps = certReq.getIpAddresses(); - if (certReqIps != null && !certReqIps.isEmpty()) { - attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_IP, String.join(",", certReqIps)); - } - if (certHostname != null) { - attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_HOSTNAME, certHostname); - } - - // we have verified our athenz and spiffe uris but we're going - // to send them all to the provider in case provider wants - // to do further verification with additional uris if any were - // included in the csr - - final List certUris = certReq.getUris(); - if (certUris != null && !certUris.isEmpty()) { - attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_URI, String.join(",", certUris)); - } // if we have a cloud account setup for this domain, we're going // to include it in the optional attributes @@ -4848,7 +4970,6 @@ InstanceConfirmation generateInstanceConfirmObject(ResourceContext ctx, final St if (awsAccount != null) { attributes.put(InstanceProvider.ZTS_INSTANCE_AWS_ACCOUNT, awsAccount); } - final String azureSubscription = cloudStore.getAzureSubscription(domain); if (azureSubscription != null) { attributes.put(InstanceProvider.ZTS_INSTANCE_AZURE_SUBSCRIPTION, azureSubscription); @@ -4861,23 +4982,14 @@ InstanceConfirmation generateInstanceConfirmObject(ResourceContext ctx, final St if (azureClient != null) { attributes.put(InstanceProvider.ZTS_INSTANCE_AZURE_CLIENT, azureClient); } - - final String gcpProject = cloudStore.getGCPProjectId(domain); if (gcpProject != null) { attributes.put(InstanceProvider.ZTS_INSTANCE_GCP_PROJECT, gcpProject); } - // if this is a class based provider then we're also going - // to provide the public key in the CSR - - if (providerScheme == InstanceProvider.Scheme.CLASS) { - attributes.put(InstanceProvider.ZTS_INSTANCE_CSR_PUBLIC_KEY, Crypto.extractX509CSRPublicKey(certReq.getCertReq())); - } - // include the hostname if one is specified - if (instanceHostname != null && !instanceHostname.isEmpty()) { + if (!StringUtil.isEmpty(instanceHostname)) { attributes.put(InstanceProvider.ZTS_INSTANCE_HOSTNAME, instanceHostname); } @@ -4888,10 +5000,42 @@ InstanceConfirmation generateInstanceConfirmObject(ResourceContext ctx, final St attributes.put(InstanceProvider.ZTS_REQUEST_PRINCIPAL, principal.getFullName()); } - if (cloud != null && !cloud.isEmpty()) { + if (!StringUtil.isEmpty(cloud)) { attributes.put(InstanceProvider.ZTS_INSTANCE_CLOUD, cloud); } + // include all the attribute if the certificate request is provided + // this will help the provider to make more informed decisions + // certificate request could be null if the request is for jwt svid + + if (certReq != null) { + attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_DNS, String.join(",", certReq.getProviderDnsNames())); + final List certReqIps = certReq.getIpAddresses(); + if (certReqIps != null && !certReqIps.isEmpty()) { + attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_IP, String.join(",", certReqIps)); + } + if (certHostname != null) { + attributes.put(InstanceProvider.ZTS_INSTANCE_CERT_HOSTNAME, certHostname); + } + + // we have verified our athenz and spiffe uris but we're going + // to send them all to the provider in case provider wants + // to do further verification with additional uris if any were + // included in the csr + + final List certUris = certReq.getUris(); + if (certUris != null && !certUris.isEmpty()) { + attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_URI, String.join(",", certUris)); + } + + // if this is a class based provider then we're also going + // to provide the public key in the CSR + + if (providerScheme == InstanceProvider.Scheme.CLASS) { + attributes.put(InstanceProvider.ZTS_INSTANCE_CSR_PUBLIC_KEY, Crypto.extractX509CSRPublicKey(certReq.getCertReq())); + } + } + instance.setAttributes(attributes); return instance; } diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/InstanceTestProvider.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/InstanceTestProvider.java index 4cb05ffe2ea..06b8b4ab10b 100644 --- a/servers/zts/src/test/java/com/yahoo/athenz/zts/InstanceTestProvider.java +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/InstanceTestProvider.java @@ -18,6 +18,8 @@ import com.yahoo.athenz.auth.KeyStore; import com.yahoo.athenz.instance.provider.InstanceConfirmation; import com.yahoo.athenz.instance.provider.InstanceProvider; +import com.yahoo.athenz.instance.provider.ProviderResourceException; + import javax.net.ssl.SSLContext; public class InstanceTestProvider implements InstanceProvider { @@ -28,6 +30,12 @@ public InstanceTestProvider() throws InstantiationException { } } + @Override + public SVIDType getSVIDType() { + final String svidType = System.getProperty("athenz.instance.test.provider.svid", "x509"); + return "x509".equalsIgnoreCase(svidType) ? SVIDType.X509 : SVIDType.JWT; + } + @Override public Scheme getProviderScheme() { final String scheme = System.getProperty("athenz.instance.test.provider.scheme", "class"); @@ -46,7 +54,14 @@ public void initialize(String provider, String endpoint, SSLContext sslContext, } @Override - public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) { + public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) throws ProviderResourceException { + if (processExceptionCheck("confirm")) { + if (processExceptionCheck("argument")) { + throw new IllegalArgumentException(); + } else { + throw new ProviderResourceException(403, "request-forbidden"); + } + } return null; } diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplPostInstanceJWTRegisterTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplPostInstanceJWTRegisterTest.java new file mode 100644 index 00000000000..b3344936489 --- /dev/null +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplPostInstanceJWTRegisterTest.java @@ -0,0 +1,852 @@ +/* + * 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.zts; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import com.yahoo.athenz.auth.KeyStore; +import com.yahoo.athenz.auth.Principal; +import com.yahoo.athenz.auth.impl.CertificateAuthority; +import com.yahoo.athenz.auth.impl.FilePrivateKeyStore; +import com.yahoo.athenz.auth.impl.SimplePrincipal; +import com.yahoo.athenz.auth.util.Crypto; +import com.yahoo.athenz.common.metrics.Metric; +import com.yahoo.athenz.common.server.rest.ServerResourceContext; +import com.yahoo.athenz.common.server.store.ChangeLogStore; +import com.yahoo.athenz.common.server.util.ResourceUtils; +import com.yahoo.athenz.common.utils.SignUtils; +import java.text.ParseException; +import com.yahoo.athenz.instance.provider.InstanceConfirmation; +import com.yahoo.athenz.instance.provider.InstanceProvider; +import com.yahoo.athenz.instance.provider.ProviderResourceException; +import com.yahoo.athenz.zms.*; +import com.yahoo.athenz.zts.store.CloudStore; +import com.yahoo.athenz.zts.store.DataStore; +import com.yahoo.athenz.zts.store.MockZMSFileChangeLogStore; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.Response; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLContext; +import java.io.File; +import java.security.PrivateKey; +import java.util.*; + +import static com.yahoo.athenz.common.ServerCommonConsts.*; +import static org.mockito.ArgumentMatchers.*; +import static org.testng.Assert.*; + +public class ZTSImplPostInstanceJWTRegisterTest { + + private ZTSImpl zts = null; + private Metric ztsMetric = null; + private DataStore store = null; + private PrivateKey privateKey = null; + private CloudStore cloudStore = null; + + private static final String ZTS_DATA_STORE_PATH = "/tmp/zts_server_unit_tests/zts_root"; + private static final String MOCKCLIENTADDR = "10.11.12.13"; + + @Mock + private HttpServletRequest mockServletRequest; + @Mock + private HttpServletResponse mockServletResponse; + + @BeforeClass + public void setupClass() { + MockitoAnnotations.openMocks(this); + Mockito.when(mockServletRequest.getRemoteAddr()).thenReturn(MOCKCLIENTADDR); + + System.setProperty(ZTSConsts.ZTS_PROP_METRIC_FACTORY_CLASS, METRIC_DEFAULT_FACTORY_CLASS); + System.setProperty(ZTSConsts.ZTS_PROP_PRIVATE_KEY_STORE_FACTORY_CLASS, + "com.yahoo.athenz.auth.impl.FilePrivateKeyStoreFactory"); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_SIGNER_FACTORY_CLASS, + "com.yahoo.athenz.zts.cert.impl.SelfCertSignerFactory"); + System.setProperty(FilePrivateKeyStore.ATHENZ_PROP_PRIVATE_KEY, + "src/test/resources/unit_test_zts_at_private.pem"); + + System.setProperty(PROP_ATHENZ_CONF, "src/test/resources/athenz-2048.conf"); + System.setProperty(ZTS_PROP_FILE_NAME, "src/test/resources/zts.properties"); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_REFRESH_IP_FNAME, + "src/test/resources/cert_refresh_ipblocks.txt"); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_ALLOWED_O_VALUES, "Athenz, Inc.|My Test Company|Athenz|Yahoo"); + System.setProperty(ZTSConsts.ZTS_PROP_NOAUTH_URI_LIST, "/zts/v1/schema,/zts/v1/status"); + System.setProperty(ZTSConsts.ZTS_PROP_VALIDATE_SERVICE_SKIP_DOMAINS, "screwdriver,rbac.*"); + System.setProperty(ZTSConsts.ZTS_PROP_OPENID_ISSUER, "https://athenz.io:4443/zts/v1"); + System.setProperty(ZTSConsts.ZTS_PROP_ID_TOKEN_MAX_TIMEOUT, "43200"); + System.setProperty(ZTSConsts.ZTS_PROP_ID_TOKEN_DEFAULT_TIMEOUT, "3600"); + + ztsMetric = new com.yahoo.athenz.common.metrics.impl.NoOpMetric(); + } + + @BeforeMethod + public void setup() { + ZTSTestUtils.deleteDirectory(new File(ZTS_DATA_STORE_PATH)); + + String privKeyName = System.getProperty(FilePrivateKeyStore.ATHENZ_PROP_PRIVATE_KEY); + File privKeyFile = new File(privKeyName); + String privKey = Crypto.encodedFile(privKeyFile); + + privateKey = Crypto.loadPrivateKey(Crypto.ybase64DecodeString(privKey)); + + System.setProperty(ZTSConsts.ZTS_PROP_CERTSIGN_BASE_URI, "https://localhost:443/certsign/v2"); + System.setProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_DEFAULT_TIMEOUT, Integer.toString(2400)); + System.setProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_MAX_TIMEOUT, Integer.toString(96000)); + System.setProperty(ZTSConsts.ZTS_PROP_AUTHORIZED_PROXY_USERS, + "user_domain.proxy-user1,user_domain.proxy-user2"); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_ALLOWED_O_VALUES, "Athenz, Inc.|My Test Company|Athenz|Yahoo"); + System.setProperty(ZTSConsts.ZTS_PROP_NOAUTH_URI_LIST, "/zts/v1/schema,/zts/v1/status"); + System.setProperty(ZTSConsts.ZTS_PROP_SELF_SIGNER_PRIVATE_KEY_FNAME, + "src/test/resources/unit_test_private_encrypted.key"); + System.setProperty(ZTSConsts.ZTS_PROP_SELF_SIGNER_PRIVATE_KEY_PASSWORD, "athenz"); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_FILE_STORE_PATH, "/tmp/zts_server_cert_store"); + System.setProperty(ZTSConsts.ZTS_PROP_VALIDATE_SERVICE_IDENTITY, "false"); + System.setProperty(ZTSConsts.ZTS_PROP_OPENID_ISSUER, "https://athenz.io:4443/zts/v1"); + System.setProperty(ZTSConsts.ZTS_PROP_CERT_REQUEST_VERIFY_IP, "true"); + System.setProperty(ZTSConsts.ZTS_PROP_PROVIDER_ENDPOINTS, ".athenz2.com,.athenz.com"); + System.setProperty("athenz.instance.test.provider.svid", "jwt"); + + ZTSTestUtils.deleteDirectory(new File("/tmp/zts_server_cert_store")); + + ChangeLogStore structStore = new MockZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + cloudStore = new CloudStore(); + + store = new DataStore(structStore, cloudStore, ztsMetric); + zts = new ZTSImpl(cloudStore, store); + ZTSImpl.serverHostName = "localhost"; + } + + @AfterMethod + public void shutdown() { + cloudStore.close(); + ZTSTestUtils.deleteDirectory(new File(ZTS_DATA_STORE_PATH)); + System.clearProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_MAX_TIMEOUT); + System.clearProperty(ZTSConsts.ZTS_PROP_ROLE_TOKEN_DEFAULT_TIMEOUT); + System.clearProperty(ZTSConsts.ZTS_PROP_PROVIDER_ENDPOINTS); + System.clearProperty("athenz.instance.test.provider.svid"); + System.clearProperty("athenz.instance.test.provider.confirm.exception"); + System.clearProperty("athenz.instance.test.provider.argument.exception"); + System.clearProperty("athenz.instance.test.provider.confirm.gateway.timeout"); + } + + private ResourceContext createResourceContext(Principal principal) { + ServerResourceContext rsrcCtx = Mockito.mock(ServerResourceContext.class); + Mockito.when(rsrcCtx.principal()).thenReturn(principal); + Mockito.when(rsrcCtx.request()).thenReturn(mockServletRequest); + Mockito.when(mockServletRequest.getRemoteAddr()).thenReturn(MOCKCLIENTADDR); + Mockito.when(mockServletRequest.isSecure()).thenReturn(true); + Mockito.when(mockServletRequest.getAttribute(anyString())).thenReturn(null); + + RsrcCtxWrapper rsrcCtxWrapper = Mockito.mock(RsrcCtxWrapper.class); + Mockito.when(rsrcCtxWrapper.context()).thenReturn(rsrcCtx); + Mockito.when(rsrcCtxWrapper.principal()).thenReturn(principal); + Mockito.when(rsrcCtxWrapper.request()).thenReturn(mockServletRequest); + Mockito.when(rsrcCtxWrapper.response()).thenReturn(mockServletResponse); + if (principal != null) { + Mockito.when(rsrcCtxWrapper.logPrincipal()).thenReturn(principal.getFullName()); + Mockito.when(rsrcCtxWrapper.getPrincipalDomain()).thenReturn(principal.getDomain()); + } + return rsrcCtxWrapper; + } + + private SignedDomain createSignedDomainWithProvider(final String domainName, final String serviceName, + final String providerName) { + + DomainData domain = new DomainData(); + domain.setName(domainName); + domain.setEnabled(true); + + com.yahoo.athenz.zms.ServiceIdentity service = new com.yahoo.athenz.zms.ServiceIdentity(); + service.setName(serviceName); + domain.setServices(Collections.singletonList(service)); + + if (providerName != null) { + com.yahoo.athenz.zms.ServiceIdentity provider = new com.yahoo.athenz.zms.ServiceIdentity(); + provider.setName(providerName); + provider.setProviderEndpoint("class://com.yahoo.athenz.zts.InstanceTestProvider"); + domain.setServices(new ArrayList<>(domain.getServices())); + domain.getServices().add(provider); + + Role role = new Role(); + role.setName(ResourceUtils.roleResourceName(domainName, "providers")); + List members = new ArrayList<>(); + members.add(new RoleMember().setMemberName(providerName)); + role.setRoleMembers(members); + domain.setRoles(Collections.singletonList(role)); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); + assertion.setResource(domainName + ":service." + serviceName); + assertion.setAction("launch"); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "providers")); + + List assertions = new ArrayList<>(); + assertions.add(assertion); + + policy.setAssertions(assertions); + policy.setName(ResourceUtils.policyResourceName(domainName, "providers")); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain(domainName); + domainPolicies.setPolicies(Collections.singletonList(policy)); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + + domain.setPolicies(signedPolicies); + } + + SignedDomain signedDomain = new SignedDomain(); + signedDomain.setDomain(domain); + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } + + private JWTClaimsSet parseIdToken(String tokenString) throws ParseException { + SignedJWT signedJWT = SignedJWT.parse(tokenString); + return signedJWT.getJWTClaimsSet(); + } + + private InstanceRegisterInformation createInstanceRegisterInformation(final String instanceId, + final String spiffeUri, Boolean spiffeSubject, final String audience, final String nonce, + final Integer expiryTime, final String keyType, final String namespace) { + + InstanceRegisterInformation info = new InstanceRegisterInformation(); + info.setJwtSVIDInstanceId(instanceId); + info.setJwtSVIDSpiffe(spiffeUri); + info.setJwtSVIDSpiffeSubject(spiffeSubject); + info.setJwtSVIDAudience(audience); + info.setJwtSVIDNonce(nonce); + info.setExpiryTime(expiryTime); + info.setJwtSVIDKeyType(keyType); + info.setNamespace(namespace); + return info; + } + + @Test + public void testPostInstanceJWTRegisterSuccess() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-001", "spiffe://athenz/sa/production", + false, "audience-123", "nonce-456", 3600, null, null); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + assertNotNull(response.getEntity()); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + assertEquals(identity.getName(), "athenz.production"); + assertEquals(identity.getProvider(), "athenz.provider"); + assertEquals(identity.getInstanceId(), "instance-001"); + assertNotNull(identity.getServiceToken()); + + // Verify Location header + String location = response.getHeaderString("Location"); + assertNotNull(location); + assertEquals(location, "/zts/v1/instance/athenz.provider/athenz/production/instance-001"); + + // Verify IdToken can be parsed + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + assertEquals(idToken.getAudience().get(0), "audience-123"); + assertEquals(idToken.getStringClaim("nonce"), "nonce-456"); + assertEquals(idToken.getSubject(), "athenz.production"); + assertEquals(idToken.getStringClaim("spiffe"), "spiffe://athenz/sa/production"); + } + + @Test + public void testPostInstanceJWTRegisterWithSpiffeSubject() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-002", + "spiffe://athenz/sa/production", true, "audience-123", "nonce-456", 3600, null, null); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + assertEquals(idToken.getSubject(), "spiffe://athenz/sa/production"); + assertNull(idToken.getStringClaim("spiffe")); // Should not be set when subject is spiffe + } + + @Test + public void testPostInstanceJWTRegisterInvalidSpiffeUri() { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-003", + "spiffe://wrong-domain/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + + try { + zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.BAD_REQUEST); + assertTrue(ex.getMessage().contains("SPIFFE URI validation failed")); + } + } + + @Test + public void testPostInstanceJWTRegisterMissingSpiffeUriWhenRequired() { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-004", null, true, + "audience-123", "nonce-456", 3600, null, null); + + try { + zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.BAD_REQUEST); + assertTrue(ex.getMessage().contains("SPIFFE URI is required when jwtSVIDSpiffeSubject is true")); + } + } + + @Test + public void testPostInstanceJWTRegisterProviderNotFound() { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", null); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-005", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + + try { + zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "unknown.provider", "postInstanceJWTRegister"); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.BAD_REQUEST); + assertTrue(ex.getMessage().contains("unable to get instance for provider")); + } + } + + @Test + public void testPostInstanceJWTRegisterProviderConfirmationFailure() { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + // Configure provider to throw exception + System.setProperty("athenz.instance.test.provider.confirm.exception", "true"); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-006", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + + try { + zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("unable to verify attestation data")); + assertTrue(ex.getMessage().contains("request-forbidden")); + } + + // now let's throw an illegal argument exception + + System.setProperty("athenz.instance.test.provider.argument.exception", "true"); + try { + zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("unable to verify attestation data")); + assertFalse(ex.getMessage().contains("request-forbidden")); + } + } + + @Test + public void testPostInstanceJWTRegisterProviderGatewayTimeout() { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + // Create a custom provider that throws GatewayTimeout + InstanceProvider testProvider = new InstanceProvider() { + + @Override + public SVIDType getSVIDType() { + return SVIDType.JWT; + } + + @Override + public Scheme getProviderScheme() { + return Scheme.CLASS; + } + + @Override + public void initialize(String provider, String endpoint, SSLContext sslContext, KeyStore keyStore) { + } + + @Override + public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) throws ProviderResourceException { + throw new ProviderResourceException(ProviderResourceException.GATEWAY_TIMEOUT, "Gateway timeout"); + } + + @Override + public InstanceConfirmation refreshInstance(InstanceConfirmation confirmation) { + return null; + } + }; + + // Replace the provider manager's getProvider to return our custom provider + InstanceProviderManager originalManager = zts.instanceProviderManager; + InstanceProviderManager mockManager = Mockito.mock(InstanceProviderManager.class); + Mockito.when(mockManager.getProvider(Mockito.eq("athenz.provider"), Mockito.any())).thenReturn(testProvider); + zts.instanceProviderManager = mockManager; + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-007", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + + try { + zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.GATEWAY_TIMEOUT); + assertTrue(ex.getMessage().contains("unable to verify attestation data")); + } finally { + zts.instanceProviderManager = originalManager; + testProvider.close(); + } + } + + @Test + public void testPostInstanceJWTRegisterWithRSAKeyType() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-008", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, "RSA", null); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + assertNotNull(identity.getServiceToken()); + + // Verify token can be parsed + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + assertNotNull(idToken); + } + + @Test + public void testPostInstanceJWTRegisterWithECKeyType() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-009", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, "EC", null + ); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + assertNotNull(identity.getServiceToken()); + + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + assertNotNull(idToken); + } + + @Test + public void testPostInstanceJWTRegisterWithCustomExpiryTime() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + int customExpiry = 7200; // 2 hours + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-010", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", customExpiry, null, null + ); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + + // Verify expiry time is approximately correct (within 5 seconds tolerance) + long expectedExpiry = (System.currentTimeMillis() / 1000) + customExpiry; + assertTrue(Math.abs(idToken.getExpirationTime().getTime() / 1000 - expectedExpiry) < 5); + } + + @Test + public void testPostInstanceJWTRegisterWithLargeExpiryTime() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + int largeExpiry = 100000; // Larger than max timeout + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-012", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", largeExpiry, null, null); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + + // Should be capped at max timeout (12 hours = 43200 seconds) + long expectedExpiry = (System.currentTimeMillis() / 1000) + 43200; + assertTrue(Math.abs(idToken.getExpirationTime().getTime() / 1000 - expectedExpiry) < 5); + } + + @Test + public void testPostInstanceJWTRegisterWithoutSpiffeUri() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-013", null, false, + "audience-123", "nonce-456", 3600, null, null); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + assertEquals(idToken.getSubject(), "athenz.production"); + assertNull(idToken.getStringClaim("spiffe")); + } + + @Test + public void testPostInstanceJWTRegisterWithNamespace() { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-014", + "spiffe://athenz.io/ns/prod/sa/athenz.production", false, "audience-123", "nonce-456", 3600, null, "prod"); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + assertNotNull(identity.getServiceToken()); + } + + @Test + public void testPostInstanceJWTRegisterAccessLogAttribute() { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-015", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + + zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + // Verify that access log attribute was set + Mockito.verify(mockServletRequest, Mockito.atLeastOnce()).setAttribute( + Mockito.eq("com.yahoo.athenz.uri.addl_query"), + Mockito.anyString() + ); + } + + @Test + public void testPostInstanceJWTRegisterIdTokenFields() throws ParseException { + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + String audience = "test-audience"; + String nonce = "test-nonce"; + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-016", + "spiffe://athenz/sa/production", false, audience, nonce, 3600, null, null); + + Response response = zts.postInstanceJWTRegister(ctx, info, "athenz", "production", + "athenz.production", "athenz", "athenz.provider", "postInstanceJWTRegister"); + + assertEquals(response.getStatus(), ResourceException.CREATED); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + + // Verify all IdToken fields + assertEquals(idToken.getIntegerClaim("ver"), Integer.valueOf(1)); + assertEquals(idToken.getAudience().get(0), audience); + assertEquals(idToken.getStringClaim("nonce"), nonce); + assertEquals(idToken.getIssuer(), "https://athenz.io:4443/zts/v1"); + assertEquals(idToken.getSubject(), "athenz.production"); + assertEquals(idToken.getStringClaim("spiffe"), "spiffe://athenz/sa/production"); + assertNotNull(idToken.getIssueTime()); + assertNotNull(idToken.getDateClaim("auth_time")); + assertNotNull(idToken.getExpirationTime()); + assertTrue(idToken.getExpirationTime().getTime() > idToken.getIssueTime().getTime()); + } + + @Test + public void testPostInstanceRegisterInformationSuccess() throws ParseException { + + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); + store.processSignedDomain(providerDomain, false); + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-001", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + info.setDomain("athenz"); + info.setService("production"); + info.setProvider("athenz.provider"); + + Response response = zts.postInstanceRegisterInformation(ctx, info); + + assertEquals(response.getStatus(), ResourceException.CREATED); + assertNotNull(response.getEntity()); + InstanceIdentity identity = (InstanceIdentity) response.getEntity(); + assertEquals(identity.getName(), "athenz.production"); + assertEquals(identity.getProvider(), "athenz.provider"); + assertEquals(identity.getInstanceId(), "instance-001"); + assertNotNull(identity.getServiceToken()); + + // Verify Location header + String location = response.getHeaderString("Location"); + assertNotNull(location); + assertEquals(location, "/zts/v1/instance/athenz.provider/athenz/production/instance-001"); + + // Verify IdToken can be parsed + JWTClaimsSet idToken = parseIdToken(identity.getServiceToken()); + assertEquals(idToken.getAudience().get(0), "audience-123"); + assertEquals(idToken.getStringClaim("nonce"), "nonce-456"); + assertEquals(idToken.getSubject(), "athenz.production"); + assertEquals(idToken.getStringClaim("spiffe"), "spiffe://athenz/sa/production"); + } + + @Test + public void testPostInstanceRegisterInformationFailure() throws ParseException { + + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); + store.processSignedDomain(providerDomain, false); + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-001", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + info.setDomain("athenz"); + info.setService("unknown-service"); + info.setProvider("athenz.provider"); + + try { + zts.postInstanceRegisterInformation(ctx, info); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.FORBIDDEN); + assertTrue(ex.getMessage().contains("provider 'athenz.provider' not authorized to launch athenz.unknown-service instances")); + } + } + + @Test + public void testPostInstanceRegisterInformationInvalidSpiffe() throws ParseException { + + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); + store.processSignedDomain(providerDomain, false); + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-001", + "spiffe://athenz/sa/unknown", false, "audience-123", "nonce-456", 3600, null, null); + info.setDomain("athenz"); + info.setService("production"); + info.setProvider("athenz.provider"); + + try { + zts.postInstanceRegisterInformation(ctx, info); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.BAD_REQUEST); + assertTrue(ex.getMessage().contains("SPIFFE URI validation failed")); + } + } + + @Test + public void testPostInstanceRegisterInformationFailureProviderType() throws ParseException { + + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); + store.processSignedDomain(providerDomain, false); + + SignedDomain tenantDomain = createSignedDomainWithProvider("athenz", "production", "athenz.provider"); + store.processSignedDomain(tenantDomain, false); + + CertificateAuthority certAuthority = new CertificateAuthority(); + Principal principal = SimplePrincipal.create("athenz", "production", + "v=S1;d=athenz;n=production;s=signature", 0, certAuthority); + + ResourceContext ctx = createResourceContext(principal); + + InstanceRegisterInformation info = createInstanceRegisterInformation("instance-001", + "spiffe://athenz/sa/production", false, "audience-123", "nonce-456", 3600, null, null); + info.setDomain("athenz"); + info.setService("production"); + info.setProvider("athenz.provider"); + + System.setProperty("athenz.instance.test.provider.svid", "x509"); + + try { + zts.postInstanceRegisterInformation(ctx, info); + fail("Should have thrown ResourceException"); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.BAD_REQUEST); + assertTrue(ex.getMessage().contains("invalid instance provider type for JWT")); + } + + System.clearProperty("athenz.instance.test.provider.svid"); + } +} + diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java index ffbebe9369e..07a36788576 100644 --- a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java @@ -76,6 +76,7 @@ import com.yahoo.rdl.Schema; import com.yahoo.rdl.Struct; import com.yahoo.rdl.Timestamp; +import io.swagger.v3.oas.models.security.SecurityScheme; import jakarta.servlet.ServletContext; import org.eclipse.jetty.http.HttpHeader; import org.mockito.ArgumentCaptor; @@ -331,10 +332,6 @@ private Metric getMetric(){ return metric; } - private String generateRoleName(String domain, String role) { - return domain + ":role." + role; - } - private String generateGroupName(String domain, String group) { return domain + ":group." + group; } @@ -384,29 +381,29 @@ private SignedDomain createSignedDomain(String domainName, String tenantDomain, List roles = new ArrayList<>(); Role role = new Role(); - role.setName(generateRoleName(domainName, "admin")); + role.setName(ResourceUtils.roleResourceName(domainName, "admin")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.adminuser")); role.setRoleMembers(members); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "writers")); + role.setName(ResourceUtils.roleResourceName(domainName, "writers")); role.setRoleMembers(writers); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "readers")); + role.setName(ResourceUtils.roleResourceName(domainName, "readers")); role.setRoleMembers(readers); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "tenant.readers")); + role.setName(ResourceUtils.roleResourceName(domainName, "tenant.readers")); role.setTrust(tenantDomain); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain + ".admin")); + role.setName(ResourceUtils.roleResourceName(domainName, serviceName + ".tenant." + tenantDomain + ".admin")); role.setTrust(tenantDomain); roles.add(role); @@ -453,7 +450,7 @@ private SignedDomain createSignedDomain(String domainName, String tenantDomain, com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":tenant." + tenantDomain + ".*"); assertion.setAction("read"); - assertion.setRole(generateRoleName(domainName, "tenant.readers")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "tenant.readers")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -468,7 +465,7 @@ private SignedDomain createSignedDomain(String domainName, String tenantDomain, assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":service." + serviceName + ".tenant." + tenantDomain + ".*"); assertion.setAction("read"); - assertion.setRole(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain + ".admin")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, serviceName + ".tenant." + tenantDomain + ".admin")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -548,7 +545,7 @@ private SignedDomain createSignedDomainExpiration(String domainName, String serv List roles = new ArrayList<>(); String memberName = "user_domain.user1"; Role role = new Role(); - role.setName(generateRoleName(domainName, "admin")); + role.setName(ResourceUtils.roleResourceName(domainName, "admin")); List members = new ArrayList<>(); RoleMember roleMember = new RoleMember(); roleMember.setMemberName("user_domain.adminuser"); @@ -557,7 +554,7 @@ private SignedDomain createSignedDomainExpiration(String domainName, String serv roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "role1")); + role.setName(ResourceUtils.roleResourceName(domainName, "role1")); members = new ArrayList<>(); roleMember = new RoleMember(); roleMember.setMemberName(memberName); @@ -567,7 +564,7 @@ private SignedDomain createSignedDomainExpiration(String domainName, String serv roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "role2")); + role.setName(ResourceUtils.roleResourceName(domainName, "role2")); members = new ArrayList<>(); roleMember = new RoleMember(); roleMember.setMemberName(memberName); @@ -606,19 +603,19 @@ private SignedDomain createMultipleSignedDomains(String domainName, String tenan List roles = new ArrayList<>(); Role role = new Role(); - role.setName(generateRoleName(domainName, "admin")); + role.setName(ResourceUtils.roleResourceName(domainName, "admin")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.adminuser")); role.setRoleMembers(members); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain1 + ".admin")); + role.setName(ResourceUtils.roleResourceName(domainName, serviceName + ".tenant." + tenantDomain1 + ".admin")); role.setTrust(tenantDomain1); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain2 + ".admin")); + role.setName(ResourceUtils.roleResourceName(domainName, serviceName + ".tenant." + tenantDomain2 + ".admin")); role.setTrust(tenantDomain2); roles.add(role); @@ -645,7 +642,7 @@ private SignedDomain createMultipleSignedDomains(String domainName, String tenan com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":service." + serviceName + ".tenant." + tenantDomain1 + ".*"); assertion.setAction("read"); - assertion.setRole(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain1 + ".admin")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, serviceName + ".tenant." + tenantDomain1 + ".admin")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -658,7 +655,7 @@ private SignedDomain createMultipleSignedDomains(String domainName, String tenan assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":service." + serviceName + ".tenant." + tenantDomain2 + ".*"); assertion.setAction("read"); - assertion.setRole(generateRoleName(domainName, serviceName + ".tenant." + tenantDomain2 + ".admin")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, serviceName + ".tenant." + tenantDomain2 + ".admin")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -698,14 +695,14 @@ private SignedDomain createTenantSignedDomain(String domainName, String provider List roles = new ArrayList<>(); Role role = new Role(); - role.setName(generateRoleName(domainName, "admin")); + role.setName(ResourceUtils.roleResourceName(domainName, "admin")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.user")); role.setRoleMembers(members); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "tenancy." + providerDomain + "." + providerService + ".admin")); + role.setName(ResourceUtils.roleResourceName(domainName, "tenancy." + providerDomain + "." + providerService + ".admin")); members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.user100")); members.add(new RoleMember().setMemberName("user_domain.user101")); @@ -713,7 +710,7 @@ private SignedDomain createTenantSignedDomain(String domainName, String provider roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "readers")); + role.setName(ResourceUtils.roleResourceName(domainName, "readers")); members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.user100")); members.add(new RoleMember().setMemberName("user_domain.user101")); @@ -736,9 +733,9 @@ private SignedDomain createTenantSignedDomain(String domainName, String provider com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); - assertion.setResource(generateRoleName(providerDomain, "tenant.readers")); + assertion.setResource(ResourceUtils.roleResourceName(providerDomain, "tenant.readers")); assertion.setAction("assume_role"); - assertion.setRole(generateRoleName(domainName, "readers")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "readers")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -749,9 +746,9 @@ private SignedDomain createTenantSignedDomain(String domainName, String provider policy = new com.yahoo.athenz.zms.Policy(); assertion = new com.yahoo.athenz.zms.Assertion(); - assertion.setResource(generateRoleName(providerDomain, providerService + ".tenant." + domainName + ".admin")); + assertion.setResource(ResourceUtils.roleResourceName(providerDomain, providerService + ".tenant." + domainName + ".admin")); assertion.setAction("assume_role"); - assertion.setRole(generateRoleName(domainName, "tenancy." + providerDomain + "." + providerService + ".admin")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "tenancy." + providerDomain + "." + providerService + ".admin")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -789,18 +786,18 @@ private SignedDomain createSignedDomainWildCard(String domainName, String tenant List roles = new ArrayList<>(); Role role = new Role(); - role.setName(generateRoleName(domainName, "superusers")); + role.setName(ResourceUtils.roleResourceName(domainName, "superusers")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.admin_user")); role.setRoleMembers(members); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "users")); + role.setName(ResourceUtils.roleResourceName(domainName, "users")); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "netops_superusers")); + role.setName(ResourceUtils.roleResourceName(domainName, "netops_superusers")); role.setTrust(tenantDomain); roles.add(role); @@ -810,7 +807,7 @@ private SignedDomain createSignedDomainWildCard(String domainName, String tenant com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":node.*"); assertion.setAction("node_user"); - assertion.setRole(generateRoleName(domainName, "users")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "users")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -823,7 +820,7 @@ private SignedDomain createSignedDomainWildCard(String domainName, String tenant assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":node.*"); assertion.setAction("node_sudo"); - assertion.setRole(generateRoleName(domainName, "netops_superusers")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "netops_superusers")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -836,7 +833,7 @@ private SignedDomain createSignedDomainWildCard(String domainName, String tenant assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":node.*"); assertion.setAction("node_user"); - assertion.setRole(generateRoleName(domainName, "superusers")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "superusers")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -875,14 +872,14 @@ private SignedDomain createTenantSignedDomainWildCard(String domainName) { List roles = new ArrayList<>(); Role role = new Role(); - role.setName(generateRoleName(domainName, "superusers")); + role.setName(ResourceUtils.roleResourceName(domainName, "superusers")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.siteops_user_1")); role.setRoleMembers(members); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "users")); + role.setName(ResourceUtils.roleResourceName(domainName, "users")); roles.add(role); List policies = new ArrayList<>(); @@ -891,7 +888,7 @@ private SignedDomain createTenantSignedDomainWildCard(String domainName) { com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource("*:role.netops_superusers"); assertion.setAction("assume_role"); - assertion.setRole(generateRoleName(domainName, "superusers")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "superusers")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -904,7 +901,7 @@ private SignedDomain createTenantSignedDomainWildCard(String domainName) { assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + "netops:node.*"); assertion.setAction("node_user"); - assertion.setRole(generateRoleName(domainName, "users")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "users")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -917,7 +914,7 @@ private SignedDomain createTenantSignedDomainWildCard(String domainName) { assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + "netops:node.*"); assertion.setAction("node_sudo"); - assertion.setRole(generateRoleName(domainName, "superusers")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "superusers")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -954,14 +951,14 @@ private SignedDomain createAwsSignedDomain(String domainName, String account) { List roles = new ArrayList<>(); Role role = new Role(); - role.setName(generateRoleName(domainName, "admin")); + role.setName(ResourceUtils.roleResourceName(domainName, "admin")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.user")); role.setRoleMembers(members); roles.add(role); role = new Role(); - role.setName(generateRoleName(domainName, "aws_role")); + role.setName(ResourceUtils.roleResourceName(domainName, "aws_role")); members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.user100")); members.add(new RoleMember().setMemberName("user_domain.user101")); @@ -974,7 +971,7 @@ private SignedDomain createAwsSignedDomain(String domainName, String account) { com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":aws_role_name"); assertion.setAction("assume_aws_role"); - assertion.setRole(generateRoleName(domainName, "aws_role")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "aws_role")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -2299,7 +2296,7 @@ public void testGetAthenzJwkNoServices() throws InterruptedException { } // process sys.auth domain without zms service - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); providerDomain.getDomain().setServices(createServices("sys.auth", "zts")); try { @@ -2330,7 +2327,7 @@ public void testGetAthenzJwkNoServices() throws InterruptedException { @Test public void testGetAthenzJWK() throws InterruptedException { - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); providerDomain.getDomain().setServices( Stream.of(createServices("sys.auth", "zts"), @@ -2381,7 +2378,7 @@ public void testAthenzJWKConfChangedNoModifyTime() { @Test public void testInvalidSysAuthService() { - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); com.yahoo.athenz.zts.ServiceIdentity ztsService = zts.sysAuthService(ZTS_SERVICE); com.yahoo.athenz.zts.ServiceIdentity zmsService = zts.sysAuthService(ZMS_SERVICE); @@ -2407,7 +2404,7 @@ public void testAthenzJWKConfChanged() { @Test public void testFillAthenzJWKConfig() { - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); providerDomain.getDomain().setServices( Stream.of(createServices("sys.auth", "zts"), @@ -2447,7 +2444,7 @@ public void testFillAthenzJWKConfig() { @Test public void testLoadAthenzJWK() { - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); providerDomain.getDomain().setServices( Stream.of(createServices("sys.auth", "zts"), @@ -4915,76 +4912,6 @@ public void testConverToLowerCase() { AthenzObject.LIST.convertToLowerCase(null); } - private SignedDomain signedAuthorizedProviderDomain() { - - SignedDomain signedDomain = new SignedDomain(); - - List roles = new ArrayList<>(); - - Role role = new Role(); - role.setName(generateRoleName("sys.auth", "providers")); - List members = new ArrayList<>(); - members.add(new RoleMember().setMemberName("athenz.provider")); - members.add(new RoleMember().setMemberName("sys.auth.zts")); - role.setRoleMembers(members); - roles.add(role); - - List policies = new ArrayList<>(); - - com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); - com.yahoo.athenz.zms.Assertion assertion1 = new com.yahoo.athenz.zms.Assertion(); - assertion1.setResource("sys.auth:instance"); - assertion1.setAction("launch"); - assertion1.setRole("sys.auth:role.providers"); - - com.yahoo.athenz.zms.Assertion assertion2 = new com.yahoo.athenz.zms.Assertion(); - assertion2.setResource("sys.auth:dns.ostk.athenz.cloud"); - assertion2.setAction("launch"); - assertion2.setRole("sys.auth:role.providers"); - - com.yahoo.athenz.zms.Assertion assertion3 = new com.yahoo.athenz.zms.Assertion(); - assertion3.setResource("sys.auth:hostname.athenz.cloud"); - assertion3.setAction("launch"); - assertion3.setRole("sys.auth:role.providers"); - - com.yahoo.athenz.zms.Assertion assertion4 = new com.yahoo.athenz.zms.Assertion(); - assertion4.setResource("sys.auth:hostname.athenz.info"); - assertion4.setAction("launch"); - assertion4.setRole("sys.auth:role.providers"); - - List assertions = new ArrayList<>(); - assertions.add(assertion1); - assertions.add(assertion2); - assertions.add(assertion3); - assertions.add(assertion4); - - policy.setAssertions(assertions); - policy.setName("sys.auth:policy.providers"); - policies.add(policy); - - com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); - domainPolicies.setDomain("sys.auth"); - domainPolicies.setPolicies(policies); - - com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); - signedPolicies.setContents(domainPolicies); - signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); - signedPolicies.setKeyId("0"); - - DomainData domain = new DomainData(); - domain.setName("sys.auth"); - domain.setRoles(roles); - domain.setPolicies(signedPolicies); - domain.setModified(Timestamp.fromCurrentTime()); - - signedDomain.setDomain(domain); - - signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); - signedDomain.setKeyId("0"); - - return signedDomain; - } - private SignedDomain signedBootstrapTenantDomain(String provider, String domainName, String serviceName) { return signedBootstrapTenantDomain(provider, domainName, serviceName, null); @@ -4998,7 +4925,7 @@ private SignedDomain signedBootstrapTenantDomain(String provider, String domainN List roles = new ArrayList<>(); Role role = new Role(); - role.setName(generateRoleName(domainName, "providers")); + role.setName(ResourceUtils.roleResourceName(domainName, "providers")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName(provider)); role.setRoleMembers(members); @@ -5010,7 +4937,7 @@ private SignedDomain signedBootstrapTenantDomain(String provider, String domainN com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(domainName + ":service." + serviceName); assertion.setAction("launch"); - assertion.setRole(generateRoleName(domainName, "providers")); + assertion.setRole(ResourceUtils.roleResourceName(domainName, "providers")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -5054,7 +4981,7 @@ public void testPostInstanceRegisterInformation() throws IOException, ProviderRe Mockito.when(mockCloudStore.getGCPProjectId("athenz")).thenReturn("my-gcp-project-xsdc"); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5066,6 +4993,7 @@ public void testPostInstanceRegisterInformation() throws IOException, ProviderRe InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certSSH", "true"); @@ -5105,6 +5033,72 @@ public void testPostInstanceRegisterInformation() throws IOException, ProviderRe ztsImpl.enableWorkloadStore = false; } + @Test + public void testPostInstanceRegisterInformationNoAttestationData() throws IOException, ProviderResourceException { + + ChangeLogStore structStore = new ZMSFileChangeLogStore("/tmp/zts_server_unit_tests/zts_root", + privateKey, "0"); + + DataStore store = new DataStore(structStore, null, ztsMetric); + Mockito.when(mockCloudStore.getAzureSubscription("athenz")).thenReturn("12345"); + Mockito.when(mockCloudStore.getGCPProjectId("athenz")).thenReturn("my-gcp-project-xsdc"); + ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); + + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); + store.processSignedDomain(providerDomain, false); + + SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); + store.processSignedDomain(tenantDomain, false); + + Path path = Paths.get("src/test/resources/athenz.instanceid.csr"); + String certCsr = new String(Files.readAllBytes(path)); + + InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); + InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); + + Map attrs = new HashMap<>(); + attrs.put("certSSH", "true"); + + InstanceConfirmation confirmation = new InstanceConfirmation() + .setDomain("athenz").setService("production").setProvider("athenz.provider") + .setAttributes(attrs); + + InstanceCertManager instanceManager = Mockito.spy(ztsImpl.instanceCertManager); + Mockito.when(instanceProviderManager.getProvider(eq("athenz.provider"), Mockito.any())).thenReturn(providerClient); + Mockito.when(providerClient.confirmInstance(Mockito.any())).thenReturn(confirmation); + Mockito.when(instanceManager.insertX509CertRecord(Mockito.any())).thenReturn(true); + + path = Paths.get("src/test/resources/athenz.instanceid.pem"); + String pem = new String(Files.readAllBytes(path)); + + InstanceIdentity identity = new InstanceIdentity().setName("athenz.production") + .setX509Certificate(pem); + Mockito.doReturn(identity).when(instanceManager).generateIdentity(Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyInt(), Mockito.any(), Mockito.any()); + + ztsImpl.instanceProviderManager = instanceProviderManager; + ztsImpl.instanceCertManager = instanceManager; + ztsImpl.enableWorkloadStore = true; + + InstanceRegisterInformation info = new InstanceRegisterInformation() + .setCsr(certCsr).setDomain("athenz").setService("production") + .setProvider("athenz.provider").setToken(true); + + ResourceContext context = createResourceContext(null); + + try { + ztsImpl.postInstanceRegisterInformation(context, info); + fail(); + } catch (ResourceException ex) { + assertEquals(ex.getCode(), ResourceException.BAD_REQUEST); + assertTrue(ex.getMessage().contains("attestation data is required for x509 certificate request")); + } + + ztsImpl.enableWorkloadStore = false; + } + @Test public void testPostInstanceRegisterInformationInvalidDomain() throws IOException { @@ -5143,7 +5137,7 @@ public void testPostInstanceRegisterInformationInvalidHostname() throws IOExcept DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5155,6 +5149,7 @@ public void testPostInstanceRegisterInformationInvalidHostname() throws IOExcept InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certSSH", "true"); @@ -5208,7 +5203,7 @@ public void testPostInstanceRegisterInformationWithHostname() throws IOException DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5220,6 +5215,7 @@ public void testPostInstanceRegisterInformationWithHostname() throws IOException InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certSSH", "true"); @@ -5270,7 +5266,7 @@ public void testPostInstanceRegisterInformationWithHostnameCnames() throws IOExc DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5282,6 +5278,7 @@ public void testPostInstanceRegisterInformationWithHostnameCnames() throws IOExc InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certSSH", "true"); @@ -5344,7 +5341,7 @@ public void testPostInstanceRegisterInformationWithHostnameInvalidCname() throws DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5356,6 +5353,7 @@ public void testPostInstanceRegisterInformationWithHostnameInvalidCname() throws InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certSSH", "true"); @@ -5409,7 +5407,7 @@ public void testPostInstanceRegisterInformationNoSSH() throws IOException, Provi DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5420,6 +5418,7 @@ public void testPostInstanceRegisterInformationNoSSH() throws IOException, Provi InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certSSH", "false"); @@ -5471,7 +5470,7 @@ public void testPostInstanceRegisterInformationSshHostCert() throws IOException, DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5485,6 +5484,7 @@ public void testPostInstanceRegisterInformationSshHostCert() throws IOException, InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certSSH", "true"); @@ -5550,7 +5550,7 @@ public void testPostInstanceRegisterInformationSubjectOU() throws IOException, P DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5561,6 +5561,7 @@ public void testPostInstanceRegisterInformationSubjectOU() throws IOException, P InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -5613,7 +5614,7 @@ public void testPostInstanceRegisterInformationSubjectOUInstanceAttrs() throws I DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5624,6 +5625,7 @@ public void testPostInstanceRegisterInformationSubjectOUInstanceAttrs() throws I InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put(InstanceProvider.ZTS_CERT_SUBJECT_OU, "Testing Domain"); @@ -5671,7 +5673,7 @@ public void testPostInstanceRegisterInformationAttrsReturned() throws IOExceptio DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5682,6 +5684,7 @@ public void testPostInstanceRegisterInformationAttrsReturned() throws IOExceptio InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map instanceAttrs = new HashMap<>(); instanceAttrs.put(InstanceProvider.ZTS_CERT_REFRESH, "false"); @@ -5729,7 +5732,7 @@ public void testPostInstanceRegisterInformationInvalidIP() throws IOException { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5768,7 +5771,7 @@ public void testPostInstanceRegisterInformationWithIPAndAccount() throws IOExcep DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5779,6 +5782,8 @@ public void testPostInstanceRegisterInformationWithIPAndAccount() throws IOExcep InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); + InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); Map confirmAttrs = new HashMap<>(); @@ -5824,7 +5829,7 @@ public void testPostInstanceRegisterInformationNoProviderClient() throws IOExcep DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5864,7 +5869,7 @@ public void testPostInstanceRegisterInformationAttestationFailure() throws IOExc DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5875,6 +5880,7 @@ public void testPostInstanceRegisterInformationAttestationFailure() throws IOExc InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Mockito.when(instanceProviderManager.getProvider(eq("athenz.provider"), Mockito.any())).thenReturn(providerClient); Mockito.when(providerClient.confirmInstance(Mockito.any())).thenThrow(new ResourceException(400)); @@ -5906,7 +5912,7 @@ public void testPostInstanceRegisterInformationNetworkFailure() throws IOExcepti DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5917,6 +5923,7 @@ public void testPostInstanceRegisterInformationNetworkFailure() throws IOExcepti InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Mockito.when(instanceProviderManager.getProvider(eq("athenz.provider"), Mockito.any())).thenReturn(providerClient); Mockito.when(providerClient.confirmInstance(Mockito.any())) @@ -5962,7 +5969,7 @@ public void testPostInstanceRegisterInformationNoAuthorizedProvider() throws IOE DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -5996,7 +6003,7 @@ public void testPostInstanceRegisterInformationServiceNotAuthorizedProvider() th DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6030,7 +6037,7 @@ public void testPostInstanceRegisterInformationInvalidCSR() { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6061,7 +6068,7 @@ public void testPostInstanceRegisterInformationCSRValidateFailure() throws IOExc DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6072,6 +6079,8 @@ public void testPostInstanceRegisterInformationCSRValidateFailure() throws IOExc InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); + InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6109,7 +6118,7 @@ public void testPostInstanceRegisterInformationIdentityFailure() throws IOExcept DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6120,6 +6129,8 @@ public void testPostInstanceRegisterInformationIdentityFailure() throws IOExcept InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); + InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6161,7 +6172,7 @@ public void testPostInstanceRegisterInformationSSHIdentityFailure() throws IOExc DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6172,6 +6183,7 @@ public void testPostInstanceRegisterInformationSSHIdentityFailure() throws IOExc InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6216,7 +6228,7 @@ public void testPostInstanceRegisterInformationCertRecordFailure() throws IOExce DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6227,6 +6239,7 @@ public void testPostInstanceRegisterInformationCertRecordFailure() throws IOExce InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6270,7 +6283,7 @@ private void testPostInstanceRefreshInformation(final String csrPath, final Stri DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6281,6 +6294,7 @@ private void testPostInstanceRefreshInformation(final String csrPath, final Stri InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6344,7 +6358,7 @@ public void testPostInstanceRefreshInformationSshHostCert() throws IOException, DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6361,6 +6375,7 @@ public void testPostInstanceRefreshInformationSshHostCert() throws IOException, InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider") .setAttributes(attrs); @@ -6436,7 +6451,7 @@ public void testPostInstanceRefreshInformationWithHostnameCnames() throws IOExce DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6458,6 +6473,7 @@ public void testPostInstanceRefreshInformationWithHostnameCnames() throws IOExce InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6535,7 +6551,7 @@ public void testGetValidatedX509CertRecordForbidden() throws IOException { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6578,7 +6594,7 @@ public void testGetValidatedX509CertRecord() throws IOException { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6618,7 +6634,7 @@ public void testPostInstanceRefreshInformationNoCertRefeshCheck() throws IOExcep DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6629,6 +6645,7 @@ public void testPostInstanceRefreshInformationNoCertRefeshCheck() throws IOExcep InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); Map attrs = new HashMap<>(); attrs.put("certRefresh", "false"); @@ -6684,7 +6701,7 @@ public void testPostInstanceRefreshInformationSubjectOU() throws IOException, Pr DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6695,6 +6712,7 @@ public void testPostInstanceRefreshInformationSubjectOU() throws IOException, Pr InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6765,7 +6783,7 @@ public void testPostInstanceRefreshInformationRefreshRequired() throws IOExcepti DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6776,6 +6794,7 @@ public void testPostInstanceRefreshInformationRefreshRequired() throws IOExcepti InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -6867,7 +6886,7 @@ public void testPostInstanceRefreshInformationForbidden() throws IOException, Pr DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6878,6 +6897,7 @@ public void testPostInstanceRefreshInformationForbidden() throws IOException, Pr InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceCertManager instanceManager = Mockito.spy(ztsImpl.instanceCertManager); @@ -6961,7 +6981,7 @@ public void testPostInstanceRefreshInformationNotFound() throws IOException, Pro DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -6972,6 +6992,7 @@ public void testPostInstanceRefreshInformationNotFound() throws IOException, Pro InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceCertManager instanceManager = Mockito.spy(ztsImpl.instanceCertManager); @@ -7028,7 +7049,7 @@ public void testPostInstanceRefreshInformationSSHFailure() throws IOException, P DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7039,6 +7060,7 @@ public void testPostInstanceRefreshInformationSSHFailure() throws IOException, P InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7096,7 +7118,7 @@ public void testPostInstanceRefreshInformationPrevSerialMatch() throws IOExcepti DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7107,6 +7129,7 @@ public void testPostInstanceRefreshInformationPrevSerialMatch() throws IOExcepti InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7161,7 +7184,7 @@ public void testPostInstanceRefreshInformationInvalidCSR() { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7205,7 +7228,7 @@ public void testPostInstanceRefreshInformationProviderNotAuthorized() throws IOE DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7242,7 +7265,7 @@ public void testPostInstanceRefreshInformationCSRValidateFailure() throws IOExce DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7253,6 +7276,7 @@ public void testPostInstanceRefreshInformationCSRValidateFailure() throws IOExce InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7310,7 +7334,7 @@ public void testPostInstanceRefreshInformationInstanceIdMismatch() throws IOExce DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7321,6 +7345,7 @@ public void testPostInstanceRefreshInformationInstanceIdMismatch() throws IOExce InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7378,7 +7403,7 @@ public void testPostInstanceRefreshInformationGetCertDBFailure() throws IOExcept DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7389,6 +7414,7 @@ public void testPostInstanceRefreshInformationGetCertDBFailure() throws IOExcept InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7441,7 +7467,7 @@ public void testPostInstanceRefreshInformationCertRecordCNMismatch() throws IOEx DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7452,6 +7478,7 @@ public void testPostInstanceRefreshInformationCertRecordCNMismatch() throws IOEx InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7511,7 +7538,7 @@ public void testPostInstanceRefreshInformationSerialMismatch() throws IOExceptio DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7522,6 +7549,7 @@ public void testPostInstanceRefreshInformationSerialMismatch() throws IOExceptio InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7581,7 +7609,7 @@ public void testPostInstanceRefreshInformationSerialMismatchRevokeMigration() th DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7592,6 +7620,7 @@ public void testPostInstanceRefreshInformationSerialMismatchRevokeMigration() th InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7647,7 +7676,7 @@ public void testPostInstanceRefreshInformationIdentityFailure() throws IOExcepti DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7658,6 +7687,7 @@ public void testPostInstanceRefreshInformationIdentityFailure() throws IOExcepti InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7715,7 +7745,7 @@ public void testPostInstanceRefreshInformationCertRecordFailure() throws IOExcep DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7726,6 +7756,7 @@ public void testPostInstanceRefreshInformationCertRecordFailure() throws IOExcep InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7917,7 +7948,7 @@ public void testPostInstanceRefreshInformationNoProviderClient() throws IOExcept DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -7928,6 +7959,7 @@ public void testPostInstanceRefreshInformationNoProviderClient() throws IOExcept InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); InstanceConfirmation confirmation = new InstanceConfirmation() .setDomain("athenz").setService("production").setProvider("athenz.provider"); @@ -7987,7 +8019,7 @@ public void testPostInstanceRefreshInformationSSH() throws IOException { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -8038,7 +8070,7 @@ public void testPostInstanceRefreshInformationSSHMatchPrevSerial() throws IOExce DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -8089,7 +8121,7 @@ public void testPostInstanceRefreshInformationSSHIdentityFailure() throws IOExce DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -9526,7 +9558,7 @@ public void testPostSSHRequest() throws IOException { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -9582,7 +9614,7 @@ public void testPostSSHRequestException() throws IOException { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -11036,7 +11068,7 @@ SignedDomain createGroupNewsDomain(final String newsDomainName, final String wea // create the admin role Role role = new Role(); - role.setName(generateRoleName(newsDomainName, "admin")); + role.setName(ResourceUtils.roleResourceName(newsDomainName, "admin")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.adminuser")); role.setRoleMembers(members); @@ -11069,7 +11101,7 @@ SignedDomain createGroupNewsDomain(final String newsDomainName, final String wea Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(newsDomainName + ".*"); assertion.setAction("*"); - assertion.setRole(generateRoleName(newsDomainName, "admin")); + assertion.setRole(ResourceUtils.roleResourceName(newsDomainName, "admin")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -11115,7 +11147,7 @@ void testGetRoleAccessWithDelegatedRolesWithGroups(final String roleName, final // create the admin role Role role = new Role(); - role.setName(generateRoleName(weatherDomainName, "admin")); + role.setName(ResourceUtils.roleResourceName(weatherDomainName, "admin")); List members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.adminuser")); role.setRoleMembers(members); @@ -11123,7 +11155,7 @@ void testGetRoleAccessWithDelegatedRolesWithGroups(final String roleName, final // create the trusted role - roles.add(new Role().setName(generateRoleName(weatherDomainName, roleName)).setTrust(sportsDomainName)); + roles.add(new Role().setName(ResourceUtils.roleResourceName(weatherDomainName, roleName)).setTrust(sportsDomainName)); // no services @@ -11137,7 +11169,7 @@ void testGetRoleAccessWithDelegatedRolesWithGroups(final String roleName, final com.yahoo.athenz.zms.Assertion assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(weatherDomainName + ".*"); assertion.setAction("*"); - assertion.setRole(generateRoleName(weatherDomainName, "admin")); + assertion.setRole(ResourceUtils.roleResourceName(weatherDomainName, "admin")); List assertions = new ArrayList<>(); assertions.add(assertion); @@ -11177,14 +11209,14 @@ void testGetRoleAccessWithDelegatedRolesWithGroups(final String roleName, final roles = new ArrayList<>(); role = new Role(); - role.setName(generateRoleName(sportsDomainName, "admin")); + role.setName(ResourceUtils.roleResourceName(sportsDomainName, "admin")); members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.adminuser")); role.setRoleMembers(members); roles.add(role); role = new Role(); - role.setName(generateRoleName(sportsDomainName, roleName)); + role.setName(ResourceUtils.roleResourceName(sportsDomainName, roleName)); members = new ArrayList<>(); members.add(new RoleMember().setMemberName("user_domain.user2")); members.add(new RoleMember().setMemberName(ResourceUtils.groupResourceName(newsDomainName, "group1"))); @@ -11197,7 +11229,7 @@ void testGetRoleAccessWithDelegatedRolesWithGroups(final String roleName, final assertion = new com.yahoo.athenz.zms.Assertion(); assertion.setResource(sportsDomainName + ".*"); assertion.setAction("*"); - assertion.setRole(generateRoleName(sportsDomainName, "admin")); + assertion.setRole(ResourceUtils.roleResourceName(sportsDomainName, "admin")); assertions = new ArrayList<>(); assertions.add(assertion); @@ -11209,9 +11241,9 @@ void testGetRoleAccessWithDelegatedRolesWithGroups(final String roleName, final policy = new com.yahoo.athenz.zms.Policy(); assertion = new com.yahoo.athenz.zms.Assertion(); final String assumeRoleDomain = wildCardAssumeDomain ? "*" : weatherDomainName; - assertion.setResource(generateRoleName(assumeRoleDomain, assumeRoleName)); + assertion.setResource(ResourceUtils.roleResourceName(assumeRoleDomain, assumeRoleName)); assertion.setAction("assume_role"); - assertion.setRole(generateRoleName(sportsDomainName, roleName)); + assertion.setRole(ResourceUtils.roleResourceName(sportsDomainName, roleName)); assertions = new ArrayList<>(); assertions.add(assertion); @@ -11739,7 +11771,7 @@ public void testGetInstanceRegisterToken() throws ProviderResourceException { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("sys.auth.zts", "athenz", "production"); @@ -11748,6 +11780,7 @@ public void testGetInstanceRegisterToken() throws ProviderResourceException { InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); // include the principal from the request object @@ -11797,6 +11830,7 @@ public void testGetInstanceRegisterTokenInvalidDoamin() throws ProviderResourceE InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); // include the principal from the request object @@ -11835,7 +11869,7 @@ public void testGetInstanceRegisterTokenUnknownProvider() { DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -11876,7 +11910,7 @@ public void testGetInstanceRegisterTokenProviderFailure() throws ProviderResourc DataStore store = new DataStore(structStore, null, ztsMetric); ZTSImpl ztsImpl = new ZTSImpl(mockCloudStore, store); - SignedDomain providerDomain = signedAuthorizedProviderDomain(); + SignedDomain providerDomain = ZTSTestUtils.signedAuthorizedProviderDomain(privateKey); store.processSignedDomain(providerDomain, false); SignedDomain tenantDomain = signedBootstrapTenantDomain("athenz.provider", "athenz", "production"); @@ -11885,6 +11919,7 @@ public void testGetInstanceRegisterTokenProviderFailure() throws ProviderResourc InstanceProviderManager instanceProviderManager = Mockito.mock(InstanceProviderManager.class); InstanceProvider providerClient = Mockito.mock(InstanceProvider.class); Mockito.when(providerClient.getProviderScheme()).thenReturn(InstanceProvider.Scheme.CLASS); + Mockito.when(providerClient.getSVIDType()).thenReturn(InstanceProvider.SVIDType.X509); // include the principal from the request object diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java index d1b1a8cfe94..802c33424d8 100644 --- a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java @@ -512,4 +512,74 @@ public static boolean validArrayMember(String[] array, String member) { } return false; } + + public static SignedDomain signedAuthorizedProviderDomain(PrivateKey privateKey) { + + SignedDomain signedDomain = new SignedDomain(); + + List roles = new ArrayList<>(); + + Role role = new Role(); + role.setName(ResourceUtils.roleResourceName("sys.auth", "providers")); + List members = new ArrayList<>(); + members.add(new RoleMember().setMemberName("athenz.provider")); + members.add(new RoleMember().setMemberName("sys.auth.zts")); + role.setRoleMembers(members); + roles.add(role); + + List policies = new ArrayList<>(); + + com.yahoo.athenz.zms.Policy policy = new com.yahoo.athenz.zms.Policy(); + com.yahoo.athenz.zms.Assertion assertion1 = new com.yahoo.athenz.zms.Assertion(); + assertion1.setResource("sys.auth:instance"); + assertion1.setAction("launch"); + assertion1.setRole("sys.auth:role.providers"); + + com.yahoo.athenz.zms.Assertion assertion2 = new com.yahoo.athenz.zms.Assertion(); + assertion2.setResource("sys.auth:dns.ostk.athenz.cloud"); + assertion2.setAction("launch"); + assertion2.setRole("sys.auth:role.providers"); + + com.yahoo.athenz.zms.Assertion assertion3 = new com.yahoo.athenz.zms.Assertion(); + assertion3.setResource("sys.auth:hostname.athenz.cloud"); + assertion3.setAction("launch"); + assertion3.setRole("sys.auth:role.providers"); + + com.yahoo.athenz.zms.Assertion assertion4 = new com.yahoo.athenz.zms.Assertion(); + assertion4.setResource("sys.auth:hostname.athenz.info"); + assertion4.setAction("launch"); + assertion4.setRole("sys.auth:role.providers"); + + List assertions = new ArrayList<>(); + assertions.add(assertion1); + assertions.add(assertion2); + assertions.add(assertion3); + assertions.add(assertion4); + + policy.setAssertions(assertions); + policy.setName("sys.auth:policy.providers"); + policies.add(policy); + + com.yahoo.athenz.zms.DomainPolicies domainPolicies = new com.yahoo.athenz.zms.DomainPolicies(); + domainPolicies.setDomain("sys.auth"); + domainPolicies.setPolicies(policies); + + com.yahoo.athenz.zms.SignedPolicies signedPolicies = new com.yahoo.athenz.zms.SignedPolicies(); + signedPolicies.setContents(domainPolicies); + signedPolicies.setSignature(Crypto.sign(SignUtils.asCanonicalString(domainPolicies), privateKey)); + signedPolicies.setKeyId("0"); + + DomainData domain = new DomainData(); + domain.setName("sys.auth"); + domain.setRoles(roles); + domain.setPolicies(signedPolicies); + domain.setModified(Timestamp.fromCurrentTime()); + + signedDomain.setDomain(domain); + + signedDomain.setSignature(Crypto.sign(SignUtils.asCanonicalString(domain), privateKey)); + signedDomain.setKeyId("0"); + + return signedDomain; + } } diff --git a/servers/zts/src/test/resources/athenz-2048.conf b/servers/zts/src/test/resources/athenz-2048.conf new file mode 100644 index 00000000000..af6277fe39b --- /dev/null +++ b/servers/zts/src/test/resources/athenz-2048.conf @@ -0,0 +1,16 @@ +{ + "zmsUrl": "http://localhost:10080/", + "ztsUrl": "http://localhost:8080/", + "ztsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFveDhTWVdscFlZSFAxc05XUTlnbApnU0xBRitJQUVHSEdYT0dRd1JSZVJoNUxMZ09RVHQ0eTZJeGhtb0hWSjBvUmgvR3h0Q0Z4YmlrSlNGWC80UDdxCm5HT2tKQjF6QzhpczBxU2ZEWlQvM0QrYTVYMEw5ekRNcGRZeS9WOGFLa2xweVlJa2dFdk1uZDVHd0JHak50b2cKcERzcU9heXk2andvd3d4d0FSRDgxakNlSFBpOE02OS85VW9BdlBWMmc4WnBwMFYxYlFpS1QyTmJRQWE0QXpuUQpJVVlUemlVVlNpLysrVVE4djU5L0xIUXBFdTFycU5jbDVrYU03eldWeGhhY1EwdlJYK1I1NHFqNXBmL015dXYrCm5CSWRMRnExRWZZK1JLY1E5dnlkaklpTDA2Z2QyaVFyQ0R4ZHZzdEFhM2hyK0Q0RWxkQ1RoSlgzREw5QTNqRHgKeFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t" + } + ], + "zmsPublicKeys": [ + { + "id": "0", + "key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFveDhTWVdscFlZSFAxc05XUTlnbApnU0xBRitJQUVHSEdYT0dRd1JSZVJoNUxMZ09RVHQ0eTZJeGhtb0hWSjBvUmgvR3h0Q0Z4YmlrSlNGWC80UDdxCm5HT2tKQjF6QzhpczBxU2ZEWlQvM0QrYTVYMEw5ekRNcGRZeS9WOGFLa2xweVlJa2dFdk1uZDVHd0JHak50b2cKcERzcU9heXk2andvd3d4d0FSRDgxakNlSFBpOE02OS85VW9BdlBWMmc4WnBwMFYxYlFpS1QyTmJRQWE0QXpuUQpJVVlUemlVVlNpLysrVVE4djU5L0xIUXBFdTFycU5jbDVrYU03eldWeGhhY1EwdlJYK1I1NHFqNXBmL015dXYrCm5CSWRMRnExRWZZK1JLY1E5dnlkaklpTDA2Z2QyaVFyQ0R4ZHZzdEFhM2hyK0Q0RWxkQ1RoSlgzREw5QTNqRHgKeFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t" + } + ] +} diff --git a/utils/zts-svccert/zts-svccert.go b/utils/zts-svccert/zts-svccert.go index 65fda3ea0b4..a1d7ad4ac6b 100644 --- a/utils/zts-svccert/zts-svccert.go +++ b/utils/zts-svccert/zts-svccert.go @@ -1,3 +1,6 @@ +// Copyright The Athenz Authors +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + package main import ( diff --git a/utils/zts-svctoken/.gitignore b/utils/zts-svctoken/.gitignore new file mode 100644 index 00000000000..f02b2694a2c --- /dev/null +++ b/utils/zts-svctoken/.gitignore @@ -0,0 +1 @@ +*iml diff --git a/utils/zts-svctoken/Makefile b/utils/zts-svctoken/Makefile new file mode 100644 index 00000000000..01090aac48c --- /dev/null +++ b/utils/zts-svctoken/Makefile @@ -0,0 +1,58 @@ +# +# Makefile to build ZTS Service JWT SVID token utility +# Prerequisite: Go development environment +# +# Copyright The Athenz Authors +# Licensed under the Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0 +# + +GOPKGNAME = github.com/AthenZ/athenz/utils/zts-svctoken +PKG_DATE=$(shell date '+%Y-%m-%dT%H:%M:%S') +BINARY=zts-svctoken +SRC=zts-svctoken.go + +# check to see if go utility is installed +GO := $(shell command -v go 2> /dev/null) +GOPATH := $(shell pwd) +export $(GOPATH) + +ifdef GO + +# we need to make sure we have go 1.19+ +# the output for the go version command is: +# go version go1.19 darwin/amd64 + +GO_VER_GTEQ := $(shell expr `go version | cut -f 3 -d' ' | cut -f2 -d.` \>= 19) +ifneq "$(GO_VER_GTEQ)" "1" +all: + @echo "Please install 1.19.x or newer version of golang" +else + +.PHONY: vet fmt linux darwin +all: vet fmt linux darwin + +endif + +else + +all: + @echo "go is not available please install golang" + +endif + +vet: + go vet . + +fmt: + go fmt . + +darwin: + @echo "Building darwin client..." + GOOS=darwin go build -ldflags "-X main.VERSION=$(PKG_VERSION) -X main.BUILD_DATE=$(PKG_DATE)" -o target/darwin/$(BINARY) $(SRC) + +linux: + @echo "Building linux client..." + GOOS=linux go build -ldflags "-X main.VERSION=$(PKG_VERSION) -X main.BUILD_DATE=$(PKG_DATE)" -o target/linux/$(BINARY) $(SRC) + +clean: + rm -rf target diff --git a/utils/zts-svctoken/README.md b/utils/zts-svctoken/README.md new file mode 100644 index 00000000000..39b37a104bb --- /dev/null +++ b/utils/zts-svctoken/README.md @@ -0,0 +1,15 @@ +zts-svctoken +============ + +ZTS Service JWT SVID Client application in Go to generate service jwt tokens + +```shell +zts-svctoken -zts -domain -service -svc-key-file -svc-cert-file [-cacert ] -audience -provider -instance -nonce [-spiffe-svid] [-spiffe-uri ] +``` + +## License + +Copyright The Athenz Authors + +Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + diff --git a/utils/zts-svctoken/doc.go b/utils/zts-svctoken/doc.go new file mode 100644 index 00000000000..b2b678c7270 --- /dev/null +++ b/utils/zts-svctoken/doc.go @@ -0,0 +1,5 @@ +// Copyright The Athenz Authors +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +// zts-svctoken is a program to generate service jwt svid tokens +package main diff --git a/utils/zts-svctoken/pom.xml b/utils/zts-svctoken/pom.xml new file mode 100644 index 00000000000..f0b594ade8d --- /dev/null +++ b/utils/zts-svctoken/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + + com.yahoo.athenz + athenz + 1.12.32-SNAPSHOT + ../../pom.xml + + + zts-svctoken + jar + zts-svctoken + ZTS Service JWT SVID Utility + + + true + true + + + + + + org.codehaus.mojo + exec-maven-plugin + ${maven-exec-plugin.version} + + + + exec + + compile + + + + make + + PKG_VERSION=${project.parent.version} + clean + all + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + default-jar + + + + + + + + diff --git a/utils/zts-svctoken/zts-svctoken.go b/utils/zts-svctoken/zts-svctoken.go new file mode 100644 index 00000000000..e8abd21bd9e --- /dev/null +++ b/utils/zts-svctoken/zts-svctoken.go @@ -0,0 +1,152 @@ +// Copyright The Athenz Authors +// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms. + +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + + "github.com/AthenZ/athenz/clients/go/zts" + "github.com/AthenZ/athenz/libs/go/athenzutils" + "github.com/AthenZ/athenz/libs/go/tls/config" +) + +var ( + // VERSION gets set by the build script via the LDFLAGS. + VERSION string + + // BUILD_DATE gets set by the build script via the LDFLAGS. + BUILD_DATE string +) + +func printVersion() { + if VERSION == "" { + fmt.Println("zts-svctoken (development version)") + } else { + fmt.Println("zts-svctoken " + VERSION + " " + BUILD_DATE) + } +} + +func usage() { + fmt.Println("zts-svctoken -zts -domain -service -svc-key-file -svc-cert-file [-cacert ] -audience -provider -instance -nonce [-spiffe-uri ] [-spiffe-svid]") + os.Exit(1) +} + +func main() { + var ztsURL, domain, service, provider, instance, nonce string + var svcKeyFile, svcCertFile, caCertFile, audience, spiffeUri, attestationDataFile string + var spiffeSvid, showVersion bool + var expiryTime int + flag.StringVar(&attestationDataFile, "attestation-data", "", "Attestation Data File") + flag.BoolVar(&spiffeSvid, "spiffe-svid", false, "include spiffe uri as the subject of the token") + flag.StringVar(&spiffeUri, "spiffe-uri", "", "Spiffe URI to be included in the token") + flag.StringVar(&audience, "audience", "", "audience for the token") + flag.StringVar(&nonce, "nonce", "", "nonce for the token") + flag.IntVar(&expiryTime, "expiry-time", 0, "expiry time in minutes") + flag.StringVar(&caCertFile, "cacert", "", "CA certificate file") + flag.StringVar(&domain, "domain", "", "domain of service (required)") + flag.StringVar(&service, "service", "", "name of service (required)") + flag.StringVar(&ztsURL, "zts", "", "url of the ZTS Service") + flag.StringVar(&provider, "provider", "", "Athenz Provider") + flag.StringVar(&instance, "instance", "", "Instance Id") + flag.StringVar(&svcKeyFile, "svc-key-file", "", "service identity private key file") + flag.StringVar(&svcCertFile, "svc-cert-file", "", "service identity certificate file") + flag.BoolVar(&showVersion, "version", false, "Show version") + flag.Parse() + + if showVersion { + printVersion() + return + } + + defaultConfig, _ := athenzutils.ReadDefaultConfig() + // check to see if we need to use zts url from our default config file + if ztsURL == "" && defaultConfig != nil { + ztsURL = defaultConfig.Zts + } + + if ztsURL == "" || audience == "" || domain == "" || service == "" || provider == "" || instance == "" || svcKeyFile == "" || svcCertFile == "" || nonce == "" { + log.Println("Error: missing required attributes. Run with -help for command line arguments") + usage() + } + + if spiffeSvid && spiffeUri == "" { + log.Println("Error: spiffe-uri must be provided when spiffe-svid is set to true") + usage() + } + + // if we're given a certificate then we'll use that otherwise + // we're going to generate a ntoken for our request unless + // we're using copper argos which only uses tls and the attestation + // data contains the authentication details + + client, err := certClient(ztsURL, svcKeyFile, svcCertFile, caCertFile) + if err != nil { + log.Fatalln(err) + } + + attestationData := "" + if attestationDataFile != "" { + attestationDataBytes, err := os.ReadFile(attestationDataFile) + if err != nil { + log.Fatalln(err) + } + attestationData = string(attestationDataBytes) + } + + expiryTime32 := int32(expiryTime) + req := &zts.InstanceRegisterInformation{ + Provider: zts.ServiceName(provider), + Domain: zts.DomainName(domain), + Service: zts.SimpleName(service), + JwtSVIDInstanceId: zts.PathElement(instance), + JwtSVIDAudience: audience, + JwtSVIDNonce: zts.EntityName(nonce), + JwtSVIDSpiffe: spiffeUri, + JwtSVIDSpiffeSubject: &spiffeSvid, + ExpiryTime: &expiryTime32, + AttestationData: attestationData, + } + + // request a jwt token for this service + identity, _, err := client.PostInstanceRegisterInformation(req) + if err != nil { + log.Fatalln(err) + } + + fmt.Println(identity.ServiceToken) +} + +func certClient(ztsURL string, keyFile, certfile, caCertFile string) (*zts.ZTSClient, error) { + + keyPem, err := os.ReadFile(keyFile) + if err != nil { + return nil, err + } + certpem, err := os.ReadFile(certfile) + if err != nil { + return nil, err + } + var cacertpem []byte + if caCertFile != "" { + cacertpem, err = os.ReadFile(caCertFile) + if err != nil { + return nil, err + } + } + + tlsConfig, err := config.ClientTLSConfigFromPEM(keyPem, certpem, cacertpem) + if err != nil { + return nil, err + } + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: tlsConfig, + } + client := zts.NewClient(ztsURL, transport) + return &client, nil +}