diff --git a/Dockerfile b/Dockerfile index bfdd8e2..7f5b9c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM docker.io/library/maven:3-eclipse-temurin-11-focal as builder +FROM docker.io/library/maven:3-eclipse-temurin-17-focal as builder ARG VERSION= ARG ATHENZ_VERSION= -ARG JAVA_VERSION=11 +ARG JAVA_VERSION=17 # date -u +'%Y-%m-%dT%H:%M:%SZ' ARG BUILD_DATE # git rev-parse --short HEAD @@ -15,7 +15,7 @@ LABEL org.opencontainers.image.title="Athenz Auth Core" LABEL org.opencontainers.image.authors="ctyano " LABEL org.opencontainers.image.vendor="ctyano " LABEL org.opencontainers.image.licenses="Private" -LABEL org.opencontainers.image.url="ghcr.io/ctyano/athenz-auth-core" +LABEL org.opencontainers.image.url="ghcr.io/ctyano/athenz-plugins" LABEL org.opencontainers.image.documentation="https://www.athenz.io/" LABEL org.opencontainers.image.source="https://github.com/AthenZ/athenz" @@ -37,8 +37,8 @@ FROM docker.io/library/openjdk:22-slim-bullseye ARG VERSION -COPY --from=builder /target/athenz-auth-core-$VERSION.jar /target/athenz-auth-core-$VERSION.jar +COPY --from=builder /target/athenz-plugins-$VERSION.jar /target/athenz-plugins-$VERSION.jar ENV JAR_DESTINATION "/" -ENTRYPOINT ["/bin/sh", "-c", "cp -p /target/athenz-auth-core-*.jar ${JAR_DESTINATION}/athenz-auth-core.jar"] +ENTRYPOINT ["/bin/sh", "-c", "cp -p /target/athenz-plugins-*.jar ${JAR_DESTINATION}/athenz-plugins.jar"] diff --git a/README.md b/README.md index 5d2db1a..e19a8c8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# athenz-auth-core +# athenz-plugins This is an unofficial repository to provide tools, packages and instructions for [Athenz](https://www.athenz.io). @@ -9,7 +9,7 @@ It is currently owned and maintained by [ctyano](https://github.com/ctyano). ``` VERSION=0.0.0 ATHENZ_PACKAGE_VERSION="$(curl -s https://api.github.com/repos/AthenZ/athenz/tags | jq -r .[].name | sed -e 's/.*v\([0-9]*.[0-9]*.[0-9]*\).*/\1/g' | sort -ru | head -n1)" -docker build --build-arg VERSION=${VERSION:-0.0.0} --build-arg ATHENZ_VERSION=${ATHENZ_PACKAGE_VERSION} -t athenz-auth-core . +docker build --build-arg VERSION=${VERSION:-0.0.0} --build-arg ATHENZ_VERSION=${ATHENZ_PACKAGE_VERSION} -t athenz-plugins . ``` ## How to generate pom.xml @@ -17,7 +17,7 @@ docker build --build-arg VERSION=${VERSION:-0.0.0} --build-arg ATHENZ_VERSION=${ ``` VERSION=0.0.0 ATHENZ_VERSION="$(curl -s https://api.github.com/repos/AthenZ/athenz/tags | jq -r .[].name | sed -e 's/.*v\([0-9]*.[0-9]*.[0-9]*\).*/\1/g' | sort -ru | head -n1)" -JAVA_VERSION=11 +JAVA_VERSION=17 cat pom.xml.template \ | $HOME/.local/bin/yq -p xml -o xml ".project.version=\"${VERSION}\"" \ | $HOME/.local/bin/yq -p xml -o xml ".project.properties.\"athenz.version\"=\"${ATHENZ_VERSION}\"" \ @@ -29,9 +29,9 @@ cat pom.xml.template \ ### Docker(OCI) Image -[athenz-auth-core](https://github.com/users/ctyano/packages/container/package/athenz-auth-core) +[athenz-plugins](https://github.com/users/ctyano/packages/container/package/athenz-plugins) ### JAR class package -https://github.com/ctyano/athenz-auth-core/releases +https://github.com/ctyano/athenz-plugins/releases diff --git a/pom.xml.template b/pom.xml.template index ec40b10..902166b 100644 --- a/pom.xml.template +++ b/pom.xml.template @@ -2,10 +2,10 @@ 4.0.0 - athenz-auth-core + athenz-plugins Core Auth Interfaces io.athenz - athenz-auth-core + athenz-plugins 0.0.0 jar https://www.athenz.io @@ -173,9 +173,9 @@ - jdk11 + jdk17 - [11,) + [17,) @@ -185,16 +185,16 @@ ${maven-compiler-plugin.version} - jdk11 + jdk17 compile - 11 + 17 - ${project.basedir}/src/main/java11 + ${project.basedir}/src/main/java17 - ${project.build.outputDirectory}/META-INF/versions/11 + ${project.build.outputDirectory}/META-INF/versions/17 diff --git a/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProvider.java b/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProvider.java index 4db8c7e..21521bb 100644 --- a/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProvider.java +++ b/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProvider.java @@ -1,25 +1,26 @@ package com.yahoo.athenz.instance.provider.impl; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; 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.token.jwts.JwtsHelper; import com.yahoo.athenz.auth.token.jwts.JwtsSigningKeyResolver; -import com.yahoo.athenz.common.server.http.HttpDriver; import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigLong; import com.yahoo.athenz.instance.provider.InstanceConfirmation; import com.yahoo.athenz.instance.provider.InstanceProvider; -import com.yahoo.athenz.instance.provider.ResourceException; -import com.yahoo.rdl.JSON; -import com.yahoo.rdl.Struct; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.Jwts; +import com.yahoo.athenz.instance.provider.ProviderResourceException; import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; + +import java.net.URI; +import java.net.URISyntaxException; import java.util.*; import java.util.concurrent.TimeUnit; @@ -39,6 +40,7 @@ public class InstanceJenkinsProvider implements InstanceProvider { static final String JENKINS_PROP_ISSUER = "athenz.zts.jenkins.issuer"; static final String JENKINS_PROP_JWKS_URI = "athenz.zts.jenkins.jwks_uri"; + static final String JENKINS_RUN_ID = "sub"; static final String JENKINS_ISSUER = "https://jenkins.athenz.svc.cluster.local/oidc"; static final String JENKINS_ISSUER_JWKS_URI = "https://jenkins.athenz.svc.cluster.local/oidc/jwks"; @@ -46,8 +48,8 @@ public class InstanceJenkinsProvider implements InstanceProvider { String jenkinsIssuer = null; String provider = null; String audience = null; - JwtsSigningKeyResolver signingKeyResolver = null; - JwtsSigningKeyResolver keyStoreSigningKeyResolver = null; + String enterprise = null; + ConfigurableJWTProcessor jwtProcessor = null; Authorizer authorizer = null; DynamicConfigLong bootTimeOffsetSeconds; long certExpiryTime; @@ -69,7 +71,7 @@ public void initialize(String provider, String providerEndpoint, SSLContext sslC audience = System.getProperty(JENKINS_PROP_AUDIENCE, "athenz.io"); - // determine the dns suffix. if this is not specified we'll just default to github-actions.athenz.cloud + // determine the dns suffix. if this is not specified we'll just default to jenkins.athenz.cloud final String dnsSuffix = System.getProperty(JENKINS_PROP_PROVIDER_DNS_SUFFIX, "jenkins.athenz.io"); dnsSuffixes = new HashSet<>(); @@ -85,18 +87,13 @@ public void initialize(String provider, String providerEndpoint, SSLContext sslC certExpiryTime = Long.parseLong(System.getProperty(JENKINS_PROP_CERT_EXPIRY_MINUTES, "360")); - // initialize our jwt key resolver + // initialize our jwt processor jenkinsIssuer = System.getProperty(JENKINS_PROP_ISSUER, JENKINS_ISSUER); - signingKeyResolver = new JwtsSigningKeyResolver(extractJenkinsIssuerJwksUri(jenkinsIssuer), null); - keyStoreSigningKeyResolver = new JwtsSigningKeyResolver(null, null); - } - - HttpDriver getHttpDriver(String url) { - return new HttpDriver.Builder(url, null).build(); + jwtProcessor = JwtsHelper.getJWTProcessor(new JwtsSigningKeyResolver(extractjenkinsIssuerJwksUri(jenkinsIssuer), null)); } - String extractJenkinsIssuerJwksUri(final String issuer) { + String extractjenkinsIssuerJwksUri(final String issuer) { // if we have the value configured then that's what we're going to use @@ -108,26 +105,18 @@ String extractJenkinsIssuerJwksUri(final String issuer) { // otherwise we'll assume the issuer follows the standard and // includes the jwks uri in its openid configuration - try (HttpDriver httpDriver = getHttpDriver(issuer)) { - String openIdConfig = httpDriver.doGet("/.well-known/openid-configuration", null); - if (!StringUtil.isEmpty(openIdConfig)) { - Struct openIdConfigStruct = JSON.fromString(openIdConfig, Struct.class); - if (openIdConfigStruct != null) { - jwksUri = openIdConfigStruct.getString("jwks_uri"); - } - } - } catch (Exception ex) { - LOGGER.error("Unable to retrieve openid configuration from issuer: {}", issuer, ex); - } + final String openIdConfigUri = issuer + "/.well-known/openid-configuration"; + JwtsHelper helper = new JwtsHelper(); + jwksUri = helper.extractJwksUri(openIdConfigUri, null); // if we still don't have a value we'll just return the default value return StringUtil.isEmpty(jwksUri) ? JENKINS_ISSUER_JWKS_URI : jwksUri; } - private ResourceException forbiddenError(String message) { + private ProviderResourceException forbiddenError(String message) { LOGGER.error(message); - return new ResourceException(ResourceException.FORBIDDEN, message); + return new ProviderResourceException(ProviderResourceException.FORBIDDEN, message); } @Override @@ -136,7 +125,7 @@ public void setAuthorizer(Authorizer authorizer) { } @Override - public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) { + public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) throws ProviderResourceException { // before running any checks make sure we have a valid authorizer @@ -157,14 +146,14 @@ public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) { if (!StringUtil.isEmpty(InstanceUtils.getInstanceProperty(instanceAttributes, InstanceProvider.ZTS_INSTANCE_HOSTNAME))) { - throw forbiddenError("Request must not have any sanDNS values"); + throw forbiddenError("Request must not have any hostname values"); } // validate san URI if (!validateSanUri(InstanceUtils.getInstanceProperty(instanceAttributes, InstanceProvider.ZTS_INSTANCE_SAN_URI))) { - throw forbiddenError("Unable to validate certificate request sanURI values"); + throw forbiddenError("Unable to validate certificate request URI values"); } // we need to validate the token which is our attestation @@ -173,14 +162,14 @@ public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) { final String attestationData = confirmation.getAttestationData(); if (StringUtil.isEmpty(attestationData)) { - throw forbiddenError("Jenkins ID Token must be provided"); + throw forbiddenError("Service credentials not provided"); } StringBuilder errMsg = new StringBuilder(256); final String reqInstanceId = InstanceUtils.getInstanceProperty(instanceAttributes, InstanceProvider.ZTS_INSTANCE_ID); if (!validateOIDCToken(attestationData, instanceDomain, instanceService, reqInstanceId, errMsg)) { - throw forbiddenError("Unable to validate Certificate Request with the provided ID Token: " + errMsg.toString()); + throw forbiddenError("Unable to validate Certificate Request: " + errMsg); } // validate the certificate san DNS names @@ -192,7 +181,7 @@ public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) { } // set our cert attributes in the return object. - // for GitHub Actions we do not allow refresh of those certificates, and + // for Jenkins we do not allow refresh of those certificates, and // the issued certificate can only be used by clients and not servers Map attributes = new HashMap<>(); @@ -205,7 +194,7 @@ public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) { } @Override - public InstanceConfirmation refreshInstance(InstanceConfirmation confirmation) { + public InstanceConfirmation refreshInstance(InstanceConfirmation confirmation) throws ProviderResourceException { // we do not allow refresh of GitHub actions certificates @@ -238,70 +227,89 @@ boolean validateSanUri(final String sanUri) { boolean validateOIDCToken(final String jwToken, final String domainName, final String serviceName, final String instanceId, StringBuilder errMsg) { - Jws claims; + if (jwtProcessor == null) { + errMsg.append("JWT Processor not initialized"); + return false; + } + + JWTClaimsSet claimsSet; try { - claims = Jwts.parserBuilder() - .setSigningKeyResolver(signingKeyResolver) - .setAllowedClockSkewSeconds(60) - .build() - .parseClaimsJws(jwToken); - } catch (Exception e) { - errMsg.append("Unable to parse and validate token with JWKs: ").append(e.getMessage()); - try { - claims = Jwts.parserBuilder() - .setSigningKeyResolver(keyStoreSigningKeyResolver) - .setAllowedClockSkewSeconds(60) - .build() - .parseClaimsJws(jwToken); - } catch (Exception ex) { - errMsg.append("Unable to parse and validate token with Key Store: ").append(ex.getMessage()); - return false; - } + claimsSet = jwtProcessor.process(jwToken, null); + } catch (Exception ex) { + errMsg.append("Unable to parse and validate token: ").append(ex.getMessage()); + return false; } - // verify the issuer in set to GitHub Actions + // verify the issuer in set to Jenkins - Claims claimsBody = claims.getBody(); - if (!jenkinsIssuer.equals(claimsBody.getIssuer())) { - errMsg.append("token issuer is not Jenkins: ").append(claimsBody.getIssuer()); + if (!jenkinsIssuer.equals(claimsSet.getIssuer())) { + errMsg.append("token issuer is not Jenkins: ").append(claimsSet.getIssuer()); return false; } // verify that token audience is set for our service - if (!audience.equals(claimsBody.getAudience())) { - errMsg.append("token audience is not ZTS Server audience: ").append(claimsBody.getAudience()); + if (!audience.equals(JwtsHelper.getAudience(claimsSet))) { + errMsg.append("token audience is not ZTS Server audience: ").append(JwtsHelper.getAudience(claimsSet)); return false; } // need to verify that the issue time is within our configured bootstrap time - Date issueDate = claimsBody.getIssuedAt(); + Date issueDate = claimsSet.getIssueTime(); if (issueDate == null || issueDate.getTime() < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(bootTimeOffsetSeconds.get())) { errMsg.append("job start time is not recent enough, issued at: ").append(issueDate); return false; } + // verify that the instance id matches the repository and run id in the token + + if (!validateInstanceId(instanceId, claimsSet, errMsg)) { + return false; + } + // verify the domain and service names in the token based on our configuration - return validateTenantDomainToken(claimsBody, domainName, serviceName, errMsg); + return validateTenantDomainToken(claimsSet, domainName, serviceName, errMsg); } - boolean validateTenantDomainToken(final Claims claims, final String domainName, final String serviceName, + boolean validateInstanceId(final String instanceId, JWTClaimsSet claimsSet, StringBuilder errMsg) { + + // the format for the instance id is :: + // the repository claim in the token has the format / + // so we'll extract that value and replace / with : to match our instance id + + final String runId = JwtsHelper.getStringClaim(claimsSet, JENKINS_RUN_ID); + if (StringUtil.isEmpty(runId)) { + errMsg.append("token does not contain required subject claim"); + return false; + } + URI netUrl; + try { + netUrl = new URI(runId); + } catch (URISyntaxException e) { + errMsg.append("token does not contain valid subject claim"); + return false; + } + final String tokenInstanceId = netUrl.getHost() + netUrl.getPath().replace("/", ":"); + if (!tokenInstanceId.equals(instanceId)) { + errMsg.append("invalid instance id: ").append(tokenInstanceId).append("/").append(instanceId); + return false; + } + return true; + } + + boolean validateTenantDomainToken(final JWTClaimsSet claimsSet, final String domainName, final String serviceName, StringBuilder errMsg) { // we need to extract and generate our action value for the authz check - final String action = "jenkins.job"; + final String action = "jenkins-pipeline"; // we need to generate our resource value based on the subject - final String subject = claims.getSubject(); - if (StringUtil.isEmpty(subject)) { - errMsg.append("token does not contain required subject claim"); - return false; - } + final String subject = claimsSet.getSubject(); // generate our principal object and carry out authorization check diff --git a/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProvider.java b/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProvider.java index a445053..dfe5533 100644 --- a/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProvider.java +++ b/src/main/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProvider.java @@ -1,35 +1,28 @@ package com.yahoo.athenz.instance.provider.impl; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; import com.yahoo.athenz.auth.KeyStore; -import com.yahoo.athenz.auth.token.PrincipalToken; -import com.yahoo.athenz.auth.token.Token; +import com.yahoo.athenz.auth.token.jwts.JwtsHelper; import com.yahoo.athenz.auth.token.jwts.JwtsSigningKeyResolver; import com.yahoo.athenz.common.server.dns.HostnameResolver; -import com.yahoo.athenz.common.server.util.ResourceUtils; import com.yahoo.athenz.instance.provider.InstanceConfirmation; import com.yahoo.athenz.instance.provider.InstanceProvider; -import com.yahoo.athenz.instance.provider.ResourceException; -import com.yahoo.athenz.zts.InstanceRegisterToken; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import com.yahoo.athenz.instance.provider.ProviderResourceException; import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import java.security.PrivateKey; -import java.time.Instant; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class InstanceWorkloadIPTokenProvider implements InstanceProvider { private static final Logger LOGGER = LoggerFactory.getLogger(InstanceWorkloadIPTokenProvider.class); - private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); private static final String URI_HOSTNAME_PREFIX = "athenz://hostname/"; static final String ZTS_PROP_PROVIDER_DNS_SUFFIX = "athenz.zts.provider_dns_suffix"; @@ -54,10 +47,12 @@ public class InstanceWorkloadIPTokenProvider implements InstanceProvider { Set dnsSuffixes = null; String provider = null; String keyId = null; + JWSSigner signer = null; + JWSAlgorithm sigAlg = null; PrivateKey key = null; - SignatureAlgorithm keyAlg = null; Set principals = null; HostnameResolver hostnameResolver = null; + ConfigurableJWTProcessor jwtProcessor = null; JwtsSigningKeyResolver signingKeyResolver = null; int expiryTime; @@ -90,24 +85,17 @@ public void initialize(String provider, String providerEndpoint, SSLContext sslC dnsSuffix = "zts.athenz.cloud"; } dnsSuffixes.addAll(Arrays.asList(dnsSuffix.split(","))); - - this.keyStore = keyStore; - - // get expiry time for any generated tokens - default 30 mins - - final String expiryTimeStr = System.getProperty(ZTS_PROP_EXPIRY_TIME, "30"); - expiryTime = Integer.parseInt(expiryTimeStr); - - // initialize our jwt key resolver - - signingKeyResolver = new JwtsSigningKeyResolver(null, null); } @Override - public void setPrivateKey(PrivateKey key, String keyId, SignatureAlgorithm keyAlg) { - this.key = key; + public void setPrivateKey(PrivateKey key, String keyId, String sigAlg) { this.keyId = keyId; - this.keyAlg = keyAlg; + this.sigAlg = JWSAlgorithm.parse(sigAlg); + try { + this.signer = JwtsHelper.getJWSSigner(key); + } catch (JOSEException ex) { + throw new IllegalArgumentException("Unable to create signer: " + ex.getMessage()); + } } @Override @@ -115,22 +103,22 @@ public void setHostnameResolver(HostnameResolver hostnameResolver) { this.hostnameResolver = hostnameResolver; } - private ResourceException forbiddenError(String message) { + private ProviderResourceException forbiddenError(String message) { LOGGER.error(message); - return new ResourceException(ResourceException.FORBIDDEN, message); + return new ProviderResourceException(ProviderResourceException.FORBIDDEN, message); } @Override - public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) { + public InstanceConfirmation confirmInstance(InstanceConfirmation confirmation) throws ProviderResourceException { return validateInstanceRequest(confirmation, true); } @Override - public InstanceConfirmation refreshInstance(InstanceConfirmation confirmation) { + public InstanceConfirmation refreshInstance(InstanceConfirmation confirmation) throws ProviderResourceException { return validateInstanceRequest(confirmation, false); } - InstanceConfirmation validateInstanceRequest(InstanceConfirmation confirmation, boolean registerInstance) { + InstanceConfirmation validateInstanceRequest(InstanceConfirmation confirmation, boolean registerInstance) throws ProviderResourceException { // we need to validate the token which is our attestation // data for the service requesting a certificate @@ -139,8 +127,6 @@ InstanceConfirmation validateInstanceRequest(InstanceConfirmation confirmation, final String instanceService = confirmation.getService(); final Map instanceAttributes = confirmation.getAttributes(); - final String csrPublicKey = InstanceUtils.getInstanceProperty(instanceAttributes, - InstanceProvider.ZTS_INSTANCE_CSR_PUBLIC_KEY); // make sure this service has been configured to be supported // by this zts provider @@ -159,37 +145,11 @@ InstanceConfirmation validateInstanceRequest(InstanceConfirmation confirmation, throw forbiddenError("Service credentials not provided"); } - boolean tokenValidated; Map attributes; - StringBuilder errMsg = new StringBuilder(256); - if (attestationData.startsWith("v=S1;")) { - - // set our cert attributes in the return object - // for ZTS we do not allow refresh of those certificates - - attributes = new HashMap<>(); - attributes.put(InstanceProvider.ZTS_CERT_REFRESH, "false"); - tokenValidated = validateServiceToken(attestationData, instanceDomain, - instanceService, csrPublicKey, errMsg); + // for token based request we do support refresh operation - } else { - - // for token based request we do support refresh operation - - attributes = Collections.emptyMap(); - - final String instanceId = InstanceUtils.getInstanceProperty(instanceAttributes, - InstanceProvider.ZTS_INSTANCE_ID); - tokenValidated = validateRegisterToken(attestationData, instanceDomain, - instanceService, instanceId, InstanceUtils.getInstanceProperty(instanceAttributes, - InstanceProvider.ZTS_INSTANCE_CLIENT_IP), registerInstance, errMsg); - } - - if (!tokenValidated) { - LOGGER.error(errMsg.toString()); - throw forbiddenError("Unable to validate Certificate Request Auth Token"); - } + attributes = Collections.emptyMap(); final String clientIp = InstanceUtils.getInstanceProperty(instanceAttributes, InstanceProvider.ZTS_INSTANCE_CLIENT_IP); @@ -338,189 +298,4 @@ boolean validateSanUri(final String sanUri, final String hostname) { return true; } - - boolean validateServiceToken(final String signedToken, final String domainName, - final String serviceName, final String csrPublicKey, StringBuilder errMsg) { - - final PrincipalToken serviceToken = authenticate(signedToken, keyStore, csrPublicKey, errMsg); - if (serviceToken == null) { - return false; - } - - // verify that domain and service name match - - if (!serviceToken.getDomain().equalsIgnoreCase(domainName)) { - errMsg.append("validate failed: domain mismatch: "). - append(serviceToken.getDomain()).append(" vs. ").append(domainName); - return false; - } - - if (!serviceToken.getName().equalsIgnoreCase(serviceName)) { - errMsg.append("validate failed: service mismatch: "). - append(serviceToken.getName()).append(" vs. ").append(serviceName); - return false; - } - - return true; - } - - @Override - public InstanceRegisterToken getInstanceRegisterToken(InstanceConfirmation details) { - - // ZTS Server has already verified that the caller has update - // rights over the given service so we'll just generate - // an instance register token and return to the client - - final String principal = InstanceUtils.getInstanceProperty(details.getAttributes(), - InstanceProvider.ZTS_REQUEST_PRINCIPAL); - final String instanceId = InstanceUtils.getInstanceProperty(details.getAttributes(), - InstanceProvider.ZTS_INSTANCE_ID); - final String workloadIp = InstanceUtils.getInstanceProperty(details.getAttributes(), - InstanceWorkloadIPTokenProvider.ZTS_INSTANCE_WORKLOAD_IP); - final String tokenId = UUID.randomUUID().toString(); - - // first we'll generate and sign our token - - final String registerToken = Jwts.builder() - .setId(tokenId) - .setSubject(ResourceUtils.serviceResourceName(details.getDomain(), details.getService())) - .setIssuedAt(Date.from(Instant.now())) - .setIssuer(provider) - .setAudience(provider) - .claim(CLAIM_PROVIDER, details.getProvider()) - .claim(CLAIM_DOMAIN, details.getDomain()) - .claim(CLAIM_SERVICE, details.getService()) - .claim(CLAIM_INSTANCE_ID, instanceId) - .claim(CLAIM_CLIENT_ID, principal) - .claim(CLAIM_WORKLOAD_IP, workloadIp) - .setHeaderParam(HDR_KEY_ID, keyId) - .setHeaderParam(HDR_TOKEN_TYPE, HDR_TOKEN_JWT) - .signWith(key, keyAlg) - .compact(); - - // finally return our token to the caller - - return new InstanceRegisterToken() - .setProvider(details.getProvider()) - .setDomain(details.getDomain()) - .setService(details.getService()) - .setAttestationData(registerToken); - } - - boolean validateRegisterToken(final String jwToken, final String domainName, final String serviceName, - final String instanceId, final String clientIp, boolean registerInstance, StringBuilder errMsg) { - - Jws claims = Jwts.parserBuilder() - .setSigningKeyResolver(signingKeyResolver) - .setAllowedClockSkewSeconds(60) - .build() - .parseClaimsJws(jwToken); - - // verify that token audience is set for our service - - Claims claimsBody = claims.getBody(); - if (!ZTS_PROVIDER_SERVICE.equals(claimsBody.getAudience())) { - errMsg.append("token audience is not ZTS provider: ").append(claimsBody.getAudience()); - return false; - } - - // need to verify that the issue time is not before our expiry - // only for register requests. - - if (registerInstance) { - Date issueDate = claimsBody.getIssuedAt(); - if (issueDate == null || issueDate.getTime() < System.currentTimeMillis() - - TimeUnit.MINUTES.toMillis(expiryTime)) { - errMsg.append("token is already expired, issued at: ").append(issueDate); - return false; - } - } - - // verify provider, domain, service, and instance id values - - if (!domainName.equals(claimsBody.get(CLAIM_DOMAIN, String.class))) { - errMsg.append("invalid domain name in token: ").append(claimsBody.get(CLAIM_DOMAIN, String.class)); - return false; - } - if (!serviceName.equals(claimsBody.get(CLAIM_SERVICE, String.class))) { - errMsg.append("invalid service name in token: ").append(claimsBody.get(CLAIM_SERVICE, String.class)); - return false; - } - if (!instanceId.equals(claimsBody.get(CLAIM_INSTANCE_ID, String.class))) { - errMsg.append("invalid instance id in token: ").append(claimsBody.get(CLAIM_INSTANCE_ID, String.class)); - return false; - } - if (!clientIp.equals(claimsBody.get(CLAIM_WORKLOAD_IP, String.class))) { - errMsg.append("invalid workload ip in token: ").append(claimsBody.get(CLAIM_WORKLOAD_IP, String.class)); - return false; - } - if (!ZTS_PROVIDER_SERVICE.equals(claimsBody.get(CLAIM_PROVIDER, String.class))) { - errMsg.append("invalid provider name in token: ").append(claimsBody.get(CLAIM_PROVIDER, String.class)); - return false; - } - - return true; - } - - PrincipalToken authenticate(final String signedToken, KeyStore keyStore, - final String csrPublicKey, StringBuilder errMsg) { - - PrincipalToken serviceToken; - try { - serviceToken = new PrincipalToken(signedToken); - } catch (IllegalArgumentException ex) { - errMsg.append("authenticate failed: Invalid token: exc="). - append(ex.getMessage()).append(" : credential="). - append(Token.getUnsignedToken(signedToken)); - LOGGER.error(errMsg.toString()); - return null; - } - - // before authenticating verify that this is not an authorized - // service token - - if (serviceToken.getAuthorizedServices() != null) { - errMsg.append("authenticate failed: authorized service token") - .append(" : credential=").append(Token.getUnsignedToken(signedToken)); - LOGGER.error(errMsg.toString()); - return null; - } - - final String tokenDomain = serviceToken.getDomain().toLowerCase(); - final String tokenName = serviceToken.getName().toLowerCase(); - - // get the public key for this token to validate signature - - final String publicKey = keyStore.getPublicKey(tokenDomain, tokenName, - serviceToken.getKeyId()); - - if (!serviceToken.validate(publicKey, 300, false, errMsg)) { - return null; - } - - // finally we want to make sure the public key in the csr - // matches the public key registered in Athenz - - if (!validatePublicKeys(publicKey, csrPublicKey)) { - errMsg.append("CSR and Athenz public key mismatch"); - LOGGER.error(errMsg.toString()); - return null; - } - - return serviceToken; - } - - public boolean validatePublicKeys(final String athenzPublicKey, final String csrPublicKey) { - - // we are going to remove all whitespace, new lines - // in order to compare the pem encoded keys - - Matcher matcher = WHITESPACE_PATTERN.matcher(athenzPublicKey); - final String normAthenzPublicKey = matcher.replaceAll(""); - - matcher = WHITESPACE_PATTERN.matcher(csrPublicKey); - final String normCsrPublicKey = matcher.replaceAll(""); - - return normAthenzPublicKey.equals(normCsrPublicKey); - } } diff --git a/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProviderTest.java b/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProviderTest.java index a27e73c..052c856 100644 --- a/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProviderTest.java +++ b/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceJenkinsProviderTest.java @@ -1,121 +1,116 @@ package com.yahoo.athenz.instance.provider.impl; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; import com.yahoo.athenz.auth.Authorizer; import com.yahoo.athenz.auth.Principal; import com.yahoo.athenz.auth.impl.SimplePrincipal; import com.yahoo.athenz.auth.util.Crypto; -import com.yahoo.athenz.common.server.http.HttpDriver; import com.yahoo.athenz.instance.provider.InstanceConfirmation; import com.yahoo.athenz.instance.provider.InstanceProvider; -import com.yahoo.athenz.instance.provider.ResourceException; -import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import com.yahoo.athenz.instance.provider.ProviderResourceException; import org.mockito.Mockito; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.security.PrivateKey; +import java.security.interfaces.ECPrivateKey; import java.time.Instant; import java.util.Date; import java.util.Map; import java.util.HashMap; +import java.util.Objects; import static org.testng.Assert.*; public class InstanceJenkinsProviderTest { private final File ecPrivateKey = new File("./src/test/resources/unit_test_ec_private.key"); - private final File ecPublicKey = new File("./src/test/resources/unit_test_ec_public.key"); - private static class InstanceJenkinsProviderTestImpl extends InstanceJenkinsProvider { - - HttpDriver httpDriver; - - public void setHttpDriver(HttpDriver httpDriver) { - this.httpDriver = httpDriver; - } - - @Override - HttpDriver getHttpDriver(String url) { - return httpDriver; - } - } + private final ClassLoader classLoader = this.getClass().getClassLoader(); @AfterMethod public void tearDown() { System.clearProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI); System.clearProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE); + System.clearProperty(InstanceJenkinsProvider.JENKINS_PROP_ISSUER); + } + + static void createOpenIdConfigFile(File configFile, File jwksUri) throws IOException { + + String fileContents; + if (jwksUri == null) { + fileContents = "{}"; + } else { + fileContents = "{\n" + + " \"jwks_uri\": \"file://" + jwksUri.getCanonicalPath() + "\"\n" + + "}"; + } + Files.createDirectories(configFile.toPath().getParent()); + Files.write(configFile.toPath(), fileContents.getBytes()); } @Test public void testInitializeWithConfig() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - assertEquals(provider.signingKeyResolver.getJwksUri(), "https://config.athenz.io"); assertEquals(provider.getProviderScheme(), InstanceProvider.Scheme.CLASS); - assertNotNull(provider.getHttpDriver("https://config.athenz.io")); } @Test - public void testInitializeWithHttpDriver() throws IOException { + public void testInitializeWithOpenIdConfig() throws IOException { - // std test where the http driver will return null for the config object + File issuerFile = new File("./src/test/resources/config-openid/"); + File configFile = new File("./src/test/resources/config-openid/.well-known/openid-configuration"); + File jwksUriFile = new File("./src/test/resources/jwt-jwks.json"); + createOpenIdConfigFile(configFile, jwksUriFile); - InstanceJenkinsProviderTestImpl provider = new InstanceJenkinsProviderTestImpl(); - HttpDriver httpDriver = Mockito.mock(HttpDriver.class); - provider.setHttpDriver(httpDriver); - provider.initialize("sys.auth.jenkins", - "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - assertNotNull(provider); - assertEquals(provider.signingKeyResolver.getJwksUri(), InstanceJenkinsProvider.JENKINS_ISSUER_JWKS_URI); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_ISSUER, "file://" + issuerFile.getCanonicalPath()); - // test where the http driver will return a valid config object + // std test where the http driver will return null for the config object - provider = new InstanceJenkinsProviderTestImpl(); - httpDriver = Mockito.mock(HttpDriver.class); - Mockito.when(httpDriver.doGet("/.well-known/openid-configuration", null)) - .thenReturn("{\"jwks_uri\":\"https://athenz.io/jwks\"}"); - provider.setHttpDriver(httpDriver); + InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); assertNotNull(provider); - assertEquals(provider.signingKeyResolver.getJwksUri(), "https://athenz.io/jwks"); + Files.delete(configFile.toPath()); + } - // test when http driver return invalid data + @Test + public void testInitializeWithOpenIdConfigMissingUri() throws IOException { - provider = new InstanceJenkinsProviderTestImpl(); - httpDriver = Mockito.mock(HttpDriver.class); - Mockito.when(httpDriver.doGet("/.well-known/openid-configuration", null)) - .thenReturn("invalid-json"); - provider.setHttpDriver(httpDriver); - provider.initialize("sys.auth.jenkins", - "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - assertNotNull(provider); - assertEquals(provider.signingKeyResolver.getJwksUri(), InstanceJenkinsProvider.JENKINS_ISSUER_JWKS_URI); + File issuerFile = new File("./src/test/resources/config-openid/"); + File configFile = new File("./src/test/resources/config-openid/.well-known/openid-configuration"); + createOpenIdConfigFile(configFile, null); - // and finally throwing an exception + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_ISSUER, "file://" + issuerFile.getCanonicalPath()); + + // std test where the http driver will return null for the config object - provider = new InstanceJenkinsProviderTestImpl(); - httpDriver = Mockito.mock(HttpDriver.class); - Mockito.when(httpDriver.doGet("/.well-known/openid-configuration", null)) - .thenThrow(new IOException("invalid-json")); - provider.setHttpDriver(httpDriver); + InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); assertNotNull(provider); - assertEquals(provider.signingKeyResolver.getJwksUri(), InstanceJenkinsProvider.JENKINS_ISSUER_JWKS_URI); + Files.delete(configFile.toPath()); } @Test - public void testConfirmInstance() { + public void testConfirmInstance() throws JOSEException, ProviderResourceException { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://athenz.io"); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); @@ -124,13 +119,13 @@ public void testConfirmInstance() { Authorizer authorizer = Mockito.mock(Authorizer.class); Principal principal = SimplePrincipal.create("sports", "api", (String) null); - Mockito.when(authorizer.access("jenkins.job", "sports:https://jenkins.io/job/example-project", principal, null)) + Mockito.when(authorizer.access("jenkins-pipeline", "sports:https://jenkins.athenz.svc.cluster.local/job/test-project/1", principal, null)) .thenReturn(true); provider.setAuthorizer(authorizer); Map instanceAttributes = new HashMap<>(); - instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_ID, "athenz:sia:0001"); - instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_URI, "spiffe://ns/default/sports/api,athenz://instanceid/sys.auth.github-actions/athenz:sia:001"); + instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_ID, "jenkins.athenz.svc.cluster.local:job:test-project:1"); + instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_URI, "spiffe://ns/default/sports/api,athenz://instanceid/sys.auth.jenkins/athenz:sia:001"); instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_DNS, "api.sports.jenkins.athenz.io"); InstanceConfirmation confirmation = new InstanceConfirmation(); @@ -138,10 +133,9 @@ public void testConfirmInstance() { confirmation.setService("api"); confirmation.setProvider("sys.auth.jenkins"); confirmation.setAttestationData(generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", - System.currentTimeMillis() / 1000, false, false, false, false, false)); + System.currentTimeMillis() / 1000, false, false)); confirmation.setAttributes(instanceAttributes); - provider.signingKeyResolver.addPublicKey("0", Crypto.loadPublicKey(ecPublicKey)); InstanceConfirmation confirmResponse = provider.confirmInstance(confirmation); assertNotNull(confirmResponse); assertEquals(confirmResponse.getAttributes().get(InstanceProvider.ZTS_CERT_REFRESH), "false"); @@ -150,9 +144,10 @@ public void testConfirmInstance() { } @Test - public void testConfirmInstanceFailures() { + public void testConfirmInstanceFailuresInvalidSANEntries() throws JOSEException { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://athenz.io"); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); @@ -161,13 +156,13 @@ public void testConfirmInstanceFailures() { Authorizer authorizer = Mockito.mock(Authorizer.class); Principal principal = SimplePrincipal.create("sports", "api", (String) null); - Mockito.when(authorizer.access("jenkins.job", "sports:https://jenkins.io/job/example-project", principal, null)) + Mockito.when(authorizer.access("jenkins-pipeline", "sports:https://jenkins.athenz.svc.cluster.local/job/test-project/1", principal, null)) .thenReturn(true); provider.setAuthorizer(authorizer); Map instanceAttributes = new HashMap<>(); - instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_ID, "athenz:sia:0001"); - instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_URI, "spiffe://ns/default/sports/api,athenz://instanceid/sys.auth.jenkins/sports.api"); + instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_ID, "jenkins.athenz.svc.cluster.local:job:test-project:1"); + instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_URI, "spiffe://ns/default/sports/api,athenz://instanceid/sys.auth.jenkins/athenz:sia:001"); instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_DNS, "host1.athenz.io"); InstanceConfirmation confirmation = new InstanceConfirmation(); @@ -175,35 +170,65 @@ public void testConfirmInstanceFailures() { confirmation.setService("api"); confirmation.setProvider("sys.auth.jenkins"); confirmation.setAttestationData(generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", - System.currentTimeMillis() / 1000, false, false, false, false, false)); + System.currentTimeMillis() / 1000, false, false)); confirmation.setAttributes(instanceAttributes); - // without the public key we should get a token validation failure + // we should get a failure due to invalid san dns entry try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); - assertTrue(ex.getMessage().contains("Unable to validate Certificate Request with the provided ID Token: ")); - assertTrue(ex.getMessage().contains("Unable to parse and validate token with JWKs: A signing key must be specified if the specified JWT is digitally signed.")); + assertTrue(ex.getMessage().contains("Unable to validate certificate request sanDNS entries")); } + } + + @Test + public void testConfirmInstanceFailuresNoPublicKey() throws JOSEException { + + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks_empty.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://athenz.io"); + + InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); + provider.initialize("sys.auth.jenkins", + "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - // once we add the expected public key we should get a failure due to invalid san dns entry + Authorizer authorizer = Mockito.mock(Authorizer.class); + Principal principal = SimplePrincipal.create("sports", "api", (String) null); + Mockito.when(authorizer.access("jenkins-pipeline", "sports:https://jenkins.athenz.svc.cluster.local/job/test-project/1", principal, null)) + .thenReturn(true); + provider.setAuthorizer(authorizer); + + Map instanceAttributes = new HashMap<>(); + instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_ID, "jenkins.athenz.svc.cluster.local:job:test-project:1"); + instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_URI, "spiffe://ns/default/sports/api,athenz://instanceid/sys.auth.jenkins/athenz:sia:001"); + instanceAttributes.put(InstanceProvider.ZTS_INSTANCE_SAN_DNS, "host1.athenz.io"); + + InstanceConfirmation confirmation = new InstanceConfirmation(); + confirmation.setDomain("sports"); + confirmation.setService("api"); + confirmation.setProvider("sys.auth.jenkins"); + confirmation.setAttestationData(generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", + System.currentTimeMillis() / 1000, false, false)); + confirmation.setAttributes(instanceAttributes); + + // without the public key we should get a token validation failure - provider.signingKeyResolver.addPublicKey("0", Crypto.loadPublicKey(ecPublicKey)); try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); - assertTrue(ex.getMessage().contains("Unable to validate certificate request sanDNS entries")); + assertTrue(ex.getMessage().contains("Signed JWT rejected: Another algorithm expected, or no matching key(s) found")); } } @Test public void testConfirmInstanceWithoutAuthorizer() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); @@ -211,7 +236,7 @@ public void testConfirmInstanceWithoutAuthorizer() { try { provider.confirmInstance(null); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); assertTrue(ex.getMessage().contains("Authorizer not available")); } @@ -219,7 +244,8 @@ public void testConfirmInstanceWithoutAuthorizer() { @Test public void testConfirmInstanceWithSanIP() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); @@ -235,7 +261,7 @@ public void testConfirmInstanceWithSanIP() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); assertTrue(ex.getMessage().contains("Request must not have any sanIP addresses")); } @@ -243,7 +269,8 @@ public void testConfirmInstanceWithSanIP() { @Test public void testConfirmInstanceWithHostname() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); @@ -259,15 +286,16 @@ public void testConfirmInstanceWithHostname() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); - assertTrue(ex.getMessage().contains("Request must not have any sanDNS values")); + assertTrue(ex.getMessage().contains("Request must not have any hostname values")); } } @Test public void testConfirmInstanceWithSanURI() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); @@ -283,15 +311,16 @@ public void testConfirmInstanceWithSanURI() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); - assertTrue(ex.getMessage().contains("Unable to validate certificate request sanURI values")); + assertTrue(ex.getMessage().contains("Unable to validate certificate request URI values")); } } @Test public void testConfirmInstanceWithoutAttestationData() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); @@ -303,22 +332,23 @@ public void testConfirmInstanceWithoutAttestationData() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); - assertTrue(ex.getMessage().contains("Jenkins ID Token must be provided")); + assertTrue(ex.getMessage().contains("Service credentials not provided")); } } @Test public void testRefreshNotSupported() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); try { provider.refreshInstance(null); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); assertTrue(ex.getMessage().contains("GitHub Action X.509 Certificates cannot be refreshed")); } @@ -338,146 +368,158 @@ public void testValidateSanUri() { } @Test - public void testValidateOIDCTokenIssuerMismatch() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + public void testValidateOIDCTokenWithoutJWTProcessor() { + + InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); + + StringBuilder errMsg = new StringBuilder(256); + assertFalse(provider.validateOIDCToken("some-jwt", "sports", "api", "jenkins.athenz.svc.cluster.local:job:test-project:1", errMsg)); + assertTrue(errMsg.toString().contains("JWT Processor not initialized")); + + provider.close(); + } + + @Test + public void testValidateOIDCTokenIssuerMismatch() throws JOSEException { + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://athenz.io"); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - provider.signingKeyResolver.addPublicKey("0", Crypto.loadPublicKey(ecPublicKey)); - // our issuer will not match - String idToken = generateIdToken("https://token-actions.githubusercontent.com", - System.currentTimeMillis() / 1000, false, false, false, false, false); + String idToken = generateIdToken("https://token.actions.githubusercontent.com", + System.currentTimeMillis() / 1000, false, false); StringBuilder errMsg = new StringBuilder(256); - boolean result = provider.validateOIDCToken(idToken, "sports", "api", "athenz:sia:0001", errMsg); + boolean result = provider.validateOIDCToken(idToken, "sports", "api", "jenkins.athenz.svc.cluster.local:job:test-project:1", errMsg); assertFalse(result); assertTrue(errMsg.toString().contains("token issuer is not Jenkins")); } @Test - public void testValidateOIDCTokenAudienceMismatch() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + public void testValidateOIDCTokenAudienceMismatch() throws JOSEException { + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://test.athenz.io"); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); - provider.initialize("sys.auth.github_actions", + provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - provider.signingKeyResolver.addPublicKey("0", Crypto.loadPublicKey(ecPublicKey)); - // our audience will not match String idToken = generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", - System.currentTimeMillis() / 1000, false, false, false, false, false); + System.currentTimeMillis() / 1000, false, false); StringBuilder errMsg = new StringBuilder(256); - boolean result = provider.validateOIDCToken(idToken, "sports", "api", "athenz:sia:0001", errMsg); + boolean result = provider.validateOIDCToken(idToken, "sports", "api", "jenkins.athenz.svc.cluster.local:job:test-project:1", errMsg); assertFalse(result); assertTrue(errMsg.toString().contains("token audience is not ZTS Server audience")); } @Test - public void testValidateOIDCTokenStartNotRecentEnough() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + public void testValidateOIDCTokenStartNotRecentEnough() throws JOSEException { + + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://athenz.io"); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - provider.signingKeyResolver.addPublicKey("0", Crypto.loadPublicKey(ecPublicKey)); - // our issue time is not recent enough String idToken = generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", - System.currentTimeMillis() / 1000 - 400, false, false, false, false, false); + System.currentTimeMillis() / 1000 - 400, false, false); StringBuilder errMsg = new StringBuilder(256); - boolean result = provider.validateOIDCToken(idToken, "sports", "api", "athenz:sia:0001", errMsg); + boolean result = provider.validateOIDCToken(idToken, "sports", "api", "jenkins.athenz.svc.cluster.local:job:test-project:1", errMsg); assertFalse(result); assertTrue(errMsg.toString().contains("job start time is not recent enough")); // create another token without the issue time idToken = generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", - System.currentTimeMillis() / 1000, false, false, true, false, false); + System.currentTimeMillis() / 1000, false, true); errMsg.setLength(0); - result = provider.validateOIDCToken(idToken, "sports", "api", "athenz:sia:0001", errMsg); + result = provider.validateOIDCToken(idToken, "sports", "api", "jenkins.athenz.svc.cluster.local:job:test-project:1", errMsg); assertFalse(result); assertTrue(errMsg.toString().contains("job start time is not recent enough")); } @Test - public void testValidateOIDCTokenMissingSubject() { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + public void testValidateOIDCTokenMissingSubject() throws JOSEException { + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://athenz.io"); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - provider.signingKeyResolver.addPublicKey("0", Crypto.loadPublicKey(ecPublicKey)); - // create an id token without the subject claim String idToken = generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", - System.currentTimeMillis() / 1000, true, false, false, false, false); + System.currentTimeMillis() / 1000, true, false); StringBuilder errMsg = new StringBuilder(256); - boolean result = provider.validateOIDCToken(idToken, "sports", "api", "athenz:sia:0001", errMsg); + boolean result = provider.validateOIDCToken(idToken, "sports", "api", "jenkins.athenz.svc.cluster.local:job:test-project:1", errMsg); assertFalse(result); + assertEquals(errMsg.toString(),"token does not contain required subject claim"); assertTrue(errMsg.toString().contains("token does not contain required subject claim")); } @Test - public void testValidateOIDCTokenAuthorizationFailure() { + public void testValidateOIDCTokenAuthorizationFailure() throws JOSEException { - System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, "https://config.athenz.io"); + final String jwksUri = Objects.requireNonNull(classLoader.getResource("jwt_jwks.json")).toString(); + System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_JWKS_URI, jwksUri); System.setProperty(InstanceJenkinsProvider.JENKINS_PROP_AUDIENCE, "https://athenz.io"); InstanceJenkinsProvider provider = new InstanceJenkinsProvider(); provider.initialize("sys.auth.jenkins", "class://com.yahoo.athenz.instance.provider.impl.InstanceJenkinsProvider", null, null); - provider.signingKeyResolver.addPublicKey("0", Crypto.loadPublicKey(ecPublicKey)); - Authorizer authorizer = Mockito.mock(Authorizer.class); Principal principal = SimplePrincipal.create("sports", "api", (String) null); - Mockito.when(authorizer.access("jenkins.job", "sports:https://jenkins.io/job/example-project", principal, null)) + Mockito.when(authorizer.access("jenkins-pipeline", "sports:https://jenkins.athenz.svc.cluster.local/job/test-project/1", principal, null)) .thenReturn(false); provider.setAuthorizer(authorizer); // create an id token String idToken = generateIdToken("https://jenkins.athenz.svc.cluster.local/oidc", - System.currentTimeMillis() / 1000, false, false, false, false, false); + System.currentTimeMillis() / 1000, false, false); StringBuilder errMsg = new StringBuilder(256); - boolean result = provider.validateOIDCToken(idToken, "sports", "api", "athenz:sia:0001", errMsg); + boolean result = provider.validateOIDCToken(idToken, "sports", "api", "jenkins.athenz.svc.cluster.local:job:test-project:1", errMsg); assertFalse(result); assertTrue(errMsg.toString().contains("authorization check failed for action")); } - + private String generateIdToken(final String issuer, long currentTimeSecs, boolean skipSubject, - boolean skipEventName, boolean skipIssuedAt, boolean skipRunId, boolean skipRepository) { + boolean skipIssuedAt) throws JOSEException { PrivateKey privateKey = Crypto.loadPrivateKey(ecPrivateKey); - JwtBuilder jwtBuilder = Jwts.builder() - .setExpiration(Date.from(Instant.ofEpochSecond(currentTimeSecs + 3600))) - .setIssuer(issuer) - .setAudience("https://athenz.io"); + + JWSSigner signer = new ECDSASigner((ECPrivateKey) privateKey); + JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder() + .expirationTime(Date.from(Instant.ofEpochSecond(currentTimeSecs + 3600))) + .issuer(issuer) + .audience("https://athenz.io") + .claim("enterprise", "athenz"); if (!skipSubject) { - jwtBuilder.setSubject("https://jenkins.io/job/example-project"); - } - if (!skipEventName) { - jwtBuilder.claim("event_name", "push"); + claimsSetBuilder.subject("https://jenkins.athenz.svc.cluster.local/job/test-project/1"); } if (!skipIssuedAt) { - jwtBuilder.setIssuedAt(Date.from(Instant.ofEpochSecond(currentTimeSecs))); + claimsSetBuilder.issueTime(Date.from(Instant.ofEpochSecond(currentTimeSecs))); } - return jwtBuilder.setHeaderParam("kid", "0").signWith(privateKey, SignatureAlgorithm.ES256).compact(); + SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.ES256).keyID("eckey1").build(), + claimsSetBuilder.build()); + signedJWT.sign(signer); + return signedJWT.serialize(); } -} +} \ No newline at end of file diff --git a/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProviderTest.java b/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProviderTest.java index 727183c..139d9d3 100644 --- a/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProviderTest.java +++ b/src/test/java/com/yahoo/athenz/instance/provider/impl/InstanceWorkloadIPTokenProviderTest.java @@ -6,10 +6,8 @@ import com.yahoo.athenz.common.server.dns.HostnameResolver; import com.yahoo.athenz.instance.provider.InstanceConfirmation; import com.yahoo.athenz.instance.provider.InstanceProvider; -import com.yahoo.athenz.instance.provider.ResourceException; +import com.yahoo.athenz.instance.provider.ProviderResourceException; import com.yahoo.athenz.zts.InstanceRegisterToken; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; import org.mockito.Mockito; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -19,12 +17,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.security.PrivateKey; -import java.security.PublicKey; -import java.time.Instant; import java.util.*; -import java.util.concurrent.TimeUnit; -import static com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider.*; import static org.testng.Assert.*; import static org.testng.Assert.assertEquals; @@ -37,6 +31,7 @@ public class InstanceWorkloadIPTokenProviderTest { public void setup() throws IOException { Path path = Paths.get("./src/test/resources/public_k0.key"); servicePublicKeyStringK0 = new String(Files.readAllBytes(path)); + path = Paths.get("./src/test/resources/unit_test_private_k0.key"); servicePrivateKeyStringK0 = new String(Files.readAllBytes(path)); } @@ -44,7 +39,7 @@ public void setup() throws IOException { @Test public void testInitializeDefaults() { InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, null); assertTrue(provider.dnsSuffixes.contains("zts.athenz.cloud")); assertEquals(InstanceProvider.Scheme.CLASS, provider.getProviderScheme()); assertNull(provider.keyStore); @@ -59,7 +54,7 @@ public void testInitialize() { System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "athenz.api,sports.backend"); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, null); assertTrue(provider.dnsSuffixes.contains("zts.cloud")); assertNull(provider.keyStore); assertEquals(provider.principals.size(), 2); @@ -71,7 +66,7 @@ public void testInitialize() { System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, ""); provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, null); assertTrue(provider.dnsSuffixes.contains("zts.athenz.cloud")); assertNull(provider.keyStore); assertNull(provider.principals); @@ -80,7 +75,7 @@ public void testInitialize() { } @Test - public void testRefreshInstance() { + public void testRefreshInstance() throws ProviderResourceException { KeyStore keystore = Mockito.mock(KeyStore.class); Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); @@ -88,7 +83,7 @@ public void testRefreshInstance() { System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api"); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) @@ -115,7 +110,7 @@ public void testRefreshInstance() { public void testValidateSanIp() { InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, null); assertTrue(provider.validateSanIp(new String[]{"10.1.1.1"}, "10.1.1.1")); assertTrue(provider.validateSanIp(null, "10.1.1.1")); assertTrue(provider.validateSanIp(new String[]{}, "10.1.1.1")); @@ -150,7 +145,7 @@ public void testValidateHostname() { .thenReturn(new HashSet<>(Arrays.asList("10.1.1.1", "2001:db8:a0b:12f0:0:0:0:1"))); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, null); provider.setHostnameResolver(hostnameResolver); assertTrue(provider.validateHostname("abc.athenz.com", new String[]{"10.1.1.1"})); @@ -178,7 +173,7 @@ public void testValidateHostname() { @Test public void testValidateSanUri() { InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, null); assertTrue(provider.validateSanUri("athenz://hostname/abc.athenz.com", "abc.athenz.com")); assertTrue(provider.validateSanUri("spiffe://movies/sa/writer,athenz://hostname/abc.athenz.com", "abc.athenz.com")); assertTrue(provider.validateSanUri("spiffe://movies/sa/writer,athenz://hostname/abc.athenz.com,athenz://instanceid/zts/abc.athenz.com", "abc.athenz.com")); @@ -197,38 +192,11 @@ public void testValidateSanUri() { public void testAuthenticate() { InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); - StringBuilder errMsg = new StringBuilder(256); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, null); KeyStore keystore = Mockito.mock(KeyStore.class); Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - String token = "invalidtoken"; - assertNull(provider.authenticate(token, null, servicePublicKeyStringK0, errMsg)); - assertTrue(errMsg.toString().contains("Invalid token")); - - errMsg.setLength(0); - token = "v=S1;d=domain;n=service;t=1234;e=1235;k=0;h=host1;i=1.2.3.4;b=svc1,svc2;s=signature;bk=0;bn=svc1;bs=signature"; - assertNull(provider.authenticate(token, null, servicePublicKeyStringK0, errMsg)); - assertTrue(errMsg.toString().contains("authorized service token")); - - PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") - .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) - .expirationWindow(3600).build(); - tokenToSign.sign(servicePrivateKeyStringK0); - - errMsg.setLength(0); - assertNotNull(provider.authenticate(tokenToSign.getSignedToken(), keystore, servicePublicKeyStringK0, errMsg)); - - // test with mismatch public key - - assertNull(provider.authenticate(tokenToSign.getSignedToken(), keystore, "publicKey", errMsg)); - - // create invalid signature - - errMsg.setLength(0); - assertNull(provider.authenticate(tokenToSign.getSignedToken().replace(";s=", ";s=abc"), - keystore, servicePublicKeyStringK0, errMsg)); provider.close(); } @@ -239,39 +207,16 @@ public void testValidateToken() { Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); StringBuilder errMsg = new StringBuilder(256); - String token = "invalidtoken"; - assertFalse(provider.validateServiceToken(token, "sports", "api", servicePublicKeyStringK0, errMsg)); - assertTrue(errMsg.toString().contains("Invalid token")); - errMsg.setLength(0); - PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") - .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) - .expirationWindow(3600).build(); - tokenToSign.sign(servicePrivateKeyStringK0); - - errMsg.setLength(0); - assertTrue(provider.validateServiceToken(tokenToSign.getSignedToken(), "sports", "api", - servicePublicKeyStringK0, errMsg)); - - errMsg.setLength(0); - assertFalse(provider.validateServiceToken(tokenToSign.getSignedToken(), "sports", "ui", - servicePublicKeyStringK0, errMsg)); - assertTrue(errMsg.toString().contains("service mismatch")); - - errMsg.setLength(0); - assertFalse(provider.validateServiceToken(tokenToSign.getSignedToken(), "weather", "api", - servicePublicKeyStringK0, errMsg)); - assertTrue(errMsg.toString().contains("domain mismatch")); - provider.close(); } @Test - public void testConfirmInstance() { + public void testConfirmInstance() throws ProviderResourceException { KeyStore keystore = Mockito.mock(KeyStore.class); Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); @@ -279,7 +224,7 @@ public void testConfirmInstance() { System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api"); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) @@ -311,7 +256,7 @@ public void testConfirmInstanceUnsupportedService() { System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api"); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "backend") .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) @@ -332,7 +277,7 @@ public void testConfirmInstanceUnsupportedService() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertTrue(ex.getMessage().contains("Service not supported to be launched by ZTS Provider")); } provider.close(); @@ -340,7 +285,7 @@ public void testConfirmInstanceUnsupportedService() { } @Test - public void testConfirmInstanceValidHostname() { + public void testConfirmInstanceValidHostname() throws ProviderResourceException { KeyStore keystore = Mockito.mock(KeyStore.class); Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); @@ -352,7 +297,7 @@ public void testConfirmInstanceValidHostname() { ); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); provider.setHostnameResolver(hostnameResolver); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") @@ -380,7 +325,7 @@ public void testConfirmInstanceValidHostname() { } @Test - public void testConfirmInstanceValidHostnameIpv6() { + public void testConfirmInstanceValidHostnameIpv6() throws ProviderResourceException { KeyStore keystore = Mockito.mock(KeyStore.class); Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); @@ -392,7 +337,7 @@ public void testConfirmInstanceValidHostnameIpv6() { ); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); provider.setHostnameResolver(hostnameResolver); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") @@ -430,7 +375,7 @@ public void testConfirmInstanceUnknownHostname() { Mockito.when(hostnameResolver.getAllByName("hostabc.athenz.com")).thenReturn(new HashSet<>(Collections.singletonList("10.1.1.2"))); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); provider.setHostnameResolver(hostnameResolver); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") @@ -455,7 +400,7 @@ public void testConfirmInstanceUnknownHostname() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); assertTrue(ex.getMessage().contains("validate certificate request hostname")); } @@ -473,7 +418,7 @@ public void testConfirmInstanceInvalidHostnameUri() { Mockito.when(hostnameResolver.getAllByName("hostabc.athenz.com")).thenReturn(new HashSet<>(Collections.singletonList("10.1.1.1"))); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); provider.setHostnameResolver(hostnameResolver); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") @@ -499,7 +444,7 @@ public void testConfirmInstanceInvalidHostnameUri() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); assertTrue(ex.getMessage().contains("validate certificate request URI hostname")); } @@ -513,7 +458,7 @@ public void testConfirmInstanceInvalidIP() { Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) @@ -536,7 +481,7 @@ public void testConfirmInstanceInvalidIP() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); assertTrue(ex.getMessage().contains("validate request IP address")); } @@ -550,7 +495,7 @@ public void testConfirmInstanceInvalidDNSName() { Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); + provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) @@ -573,7 +518,7 @@ public void testConfirmInstanceInvalidDNSName() { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { + } catch (ProviderResourceException ex) { assertEquals(ex.getCode(), 403); assertTrue(ex.getMessage().contains("validate certificate request DNS")); } @@ -581,62 +526,7 @@ public void testConfirmInstanceInvalidDNSName() { } @Test - public void testConfirmInstanceInvalidToken() { - - KeyStore keystore = Mockito.mock(KeyStore.class); - Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - - PrincipalToken tokenToSign = new PrincipalToken.Builder("S1", "sports", "api") - .keyId("v0").salt("salt").issueTime(System.currentTimeMillis() / 1000) - .expirationWindow(3600).build(); - tokenToSign.sign(servicePrivateKeyStringK0); - - InstanceConfirmation confirmation = new InstanceConfirmation(); - confirmation.setAttestationData(tokenToSign.getSignedToken().replace(";s=", ";s=abc")); - confirmation.setDomain("sports"); - confirmation.setService("api"); - confirmation.setProvider("sys.auth.zts"); - - try { - provider.confirmInstance(confirmation); - fail(); - } catch (ResourceException ex) { - assertEquals(ex.getCode(), 403); - assertTrue(ex.getMessage().contains("validate Certificate Request Auth Token")); - } - provider.close(); - } - - @Test - public void testGetInstanceRegisterToken() throws IOException { - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("provider", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, null); - - Path path = Paths.get("./src/test/resources/unit_test_ec_private.key"); - final String keyPem = new String(Files.readAllBytes(path)); - - PrivateKey privateKey = Crypto.loadPrivateKey(keyPem); - provider.setPrivateKey(privateKey, "k0", SignatureAlgorithm.ES256); - - InstanceConfirmation confirmation = new InstanceConfirmation(); - confirmation.setDomain("sports"); - confirmation.setService("api"); - confirmation.setProvider("sys.auth.zts"); - Map attrs = new HashMap<>(); - attrs.put(InstanceProvider.ZTS_INSTANCE_ID, "id001"); - confirmation.setAttributes(attrs); - - InstanceRegisterToken token = provider.getInstanceRegisterToken(confirmation); - assertNotNull(token.getAttestationData()); - provider.close(); - } - - @Test - public void testConfirmInstanceWithRegisterToken() throws IOException { + public void testConfirmInstanceEmptyCredentials() { KeyStore keystore = Mockito.mock(KeyStore.class); Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); @@ -645,249 +535,8 @@ public void testConfirmInstanceWithRegisterToken() throws IOException { // get our ec public key - Path path = Paths.get("./src/test/resources/unit_test_ec_public.key"); - String keyPem = new String(Files.readAllBytes(path)); - PublicKey publicKey = Crypto.loadPublicKey(keyPem); - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("sys.auth.zts", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - provider.signingKeyResolver.addPublicKey("k0", publicKey); - - // get our private key now - - path = Paths.get("./src/test/resources/unit_test_ec_private.key"); - keyPem = new String(Files.readAllBytes(path)); - - PrivateKey privateKey = Crypto.loadPrivateKey(keyPem); - provider.setPrivateKey(privateKey, "k0", SignatureAlgorithm.ES256); - - InstanceConfirmation tokenConfirmation = new InstanceConfirmation(); - tokenConfirmation.setDomain("sports"); - tokenConfirmation.setService("api"); - tokenConfirmation.setProvider("sys.auth.zts"); - Map attrs = new HashMap<>(); - attrs.put(InstanceProvider.ZTS_INSTANCE_ID, "id001"); - attrs.put(InstanceWorkloadIPTokenProvider.ZTS_INSTANCE_WORKLOAD_IP, "127.0.0.1"); - tokenConfirmation.setAttributes(attrs); - - InstanceRegisterToken token = provider.getInstanceRegisterToken(tokenConfirmation); - - // generate instance confirmation - - InstanceConfirmation confirmation = new InstanceConfirmation(); - confirmation.setAttestationData(token.getAttestationData()); - confirmation.setDomain("sports"); - confirmation.setService("api"); - confirmation.setProvider("sys.auth.zts"); - - Map attributes = new HashMap<>(); - attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_DNS, "api.sports.zts.athenz.cloud,id001.instanceid.athenz.zts.athenz.cloud"); - attributes.put(InstanceProvider.ZTS_INSTANCE_CSR_PUBLIC_KEY, servicePublicKeyStringK0); - attributes.put(InstanceProvider.ZTS_INSTANCE_ID, "id001"); - attributes.put(InstanceWorkloadIPTokenProvider.ZTS_INSTANCE_CLIENT_IP, "127.0.0.1"); - confirmation.setAttributes(attributes); - - assertNotNull(provider.confirmInstance(confirmation)); - provider.close(); - System.clearProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST); - } - - @Test - public void testValidateRegisterTokenMismatchFields() throws IOException { - - KeyStore keystore = Mockito.mock(KeyStore.class); - Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - - System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api"); - - // get our ec public key - - Path path = Paths.get("./src/test/resources/unit_test_ec_public.key"); - String keyPem = new String(Files.readAllBytes(path)); - PublicKey publicKey = Crypto.loadPublicKey(keyPem); - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("sys.auth.zts", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - provider.signingKeyResolver.addPublicKey("k0", publicKey); - - // get our private key now - - path = Paths.get("./src/test/resources/unit_test_ec_private.key"); - keyPem = new String(Files.readAllBytes(path)); - - PrivateKey privateKey = Crypto.loadPrivateKey(keyPem); - provider.setPrivateKey(privateKey, "k0", SignatureAlgorithm.ES256); - - InstanceConfirmation tokenConfirmation = new InstanceConfirmation(); - tokenConfirmation.setDomain("sports"); - tokenConfirmation.setService("api"); - tokenConfirmation.setProvider("sys.auth.zts"); - Map attrs = new HashMap<>(); - attrs.put(InstanceProvider.ZTS_INSTANCE_ID, "id001"); - tokenConfirmation.setAttributes(attrs); - - InstanceRegisterToken token = provider.getInstanceRegisterToken(tokenConfirmation); - - // now let's use the validate method for specific cases - - StringBuilder errMsg = new StringBuilder(); - assertFalse(provider.validateRegisterToken(token.getAttestationData(), - "weather", "api", "id001", "127.0.0.1", false, errMsg)); - assertTrue(errMsg.toString().contains("invalid domain name")); - - // next service mismatch - - errMsg.setLength(0); - assertFalse(provider.validateRegisterToken(token.getAttestationData(), - "sports", "backend", "id001", "127.0.0.1", false, errMsg)); - assertTrue(errMsg.toString().contains("invalid service name")); - - // invalid instance id - - errMsg.setLength(0); - assertFalse(provider.validateRegisterToken(token.getAttestationData(), - "sports", "api", "id002", "127.0.0.1", false, errMsg)); - assertTrue(errMsg.toString().contains("invalid instance id")); - - provider.close(); - System.clearProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST); - } - - @Test - public void testConfirmInstanceWithRegisterTokenMismatchProvider() throws IOException { - - KeyStore keystore = Mockito.mock(KeyStore.class); - Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - - System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api,weather.api,sports.backend"); - - // get our ec public key - - Path path = Paths.get("./src/test/resources/unit_test_ec_public.key"); - String keyPem = new String(Files.readAllBytes(path)); - PublicKey publicKey = Crypto.loadPublicKey(keyPem); - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("athenz.zts", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - provider.signingKeyResolver.addPublicKey("k0", publicKey); - - // get our private key now - - path = Paths.get("./src/test/resources/unit_test_ec_private.key"); - keyPem = new String(Files.readAllBytes(path)); - - PrivateKey privateKey = Crypto.loadPrivateKey(keyPem); - provider.setPrivateKey(privateKey, "k0", SignatureAlgorithm.ES256); - - InstanceConfirmation tokenConfirmation = new InstanceConfirmation(); - tokenConfirmation.setDomain("sports"); - tokenConfirmation.setService("api"); - tokenConfirmation.setProvider("sys.auth.zts"); - Map attrs = new HashMap<>(); - attrs.put(InstanceProvider.ZTS_INSTANCE_ID, "id001"); - tokenConfirmation.setAttributes(attrs); - - InstanceRegisterToken token = provider.getInstanceRegisterToken(tokenConfirmation); - - // generate instance confirmation - - InstanceConfirmation confirmation = new InstanceConfirmation(); - confirmation.setAttestationData(token.getAttestationData()); - confirmation.setDomain("sports"); - confirmation.setService("api"); - confirmation.setProvider("sys.auth.zts"); - - Map attributes = new HashMap<>(); - attributes.put(InstanceProvider.ZTS_INSTANCE_SAN_DNS, "api.sports.zts.athenz.cloud,id001.instanceid.athenz.zts.athenz.cloud"); - attributes.put(InstanceProvider.ZTS_INSTANCE_CSR_PUBLIC_KEY, servicePublicKeyStringK0); - attributes.put(InstanceProvider.ZTS_INSTANCE_ID, "id001"); - confirmation.setAttributes(attributes); - - // provider mismatch - - try { - provider.confirmInstance(confirmation); - fail(); - } catch (ResourceException ex) { - assertEquals(ex.getCode(), ResourceException.FORBIDDEN); - } - - // calling validation directly should fail as well - - StringBuilder errMsg = new StringBuilder(); - assertFalse(provider.validateRegisterToken(token.getAttestationData(), - "sports", "api", "id001", "127.0.0.1", false, errMsg)); - assertTrue(errMsg.toString().contains("token audience is not ZTS provider")); - - provider.close(); - System.clearProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST); - } - - @Test - public void testValidateRegisterTokenMismatchProvider() throws IOException { - - KeyStore keystore = Mockito.mock(KeyStore.class); - Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - - System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api,weather.api,sports.backend"); - - // get our ec public key - - Path path = Paths.get("./src/test/resources/unit_test_ec_public.key"); - String keyPem = new String(Files.readAllBytes(path)); - PublicKey publicKey = Crypto.loadPublicKey(keyPem); - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("sys.auth.zts", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - provider.signingKeyResolver.addPublicKey("k0", publicKey); - - // get our private key now - - path = Paths.get("./src/test/resources/unit_test_ec_private.key"); - keyPem = new String(Files.readAllBytes(path)); - - PrivateKey privateKey = Crypto.loadPrivateKey(keyPem); - provider.setPrivateKey(privateKey, "k0", SignatureAlgorithm.ES256); - - InstanceConfirmation tokenConfirmation = new InstanceConfirmation(); - tokenConfirmation.setDomain("sports"); - tokenConfirmation.setService("api"); - tokenConfirmation.setProvider("athenz.zts"); - Map attrs = new HashMap<>(); - attrs.put(InstanceProvider.ZTS_INSTANCE_ID, "id001"); - attrs.put(InstanceWorkloadIPTokenProvider.ZTS_INSTANCE_WORKLOAD_IP, "127.0.0.1"); - tokenConfirmation.setAttributes(attrs); - - InstanceRegisterToken token = provider.getInstanceRegisterToken(tokenConfirmation); - - // calling validation directly should fail with invalid provider - - StringBuilder errMsg = new StringBuilder(); - assertFalse(provider.validateRegisterToken(token.getAttestationData(), - "sports", "api", "id001", "127.0.0.1", false, errMsg)); - assertTrue(errMsg.toString().contains("invalid provider name")); - - provider.close(); - System.clearProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST); - } - - @Test - public void testConfirmInstanceEmptyCredentials() throws IOException { - - KeyStore keystore = Mockito.mock(KeyStore.class); - Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - - System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api"); - - // get our ec public key - - Path path = Paths.get("./src/test/resources/unit_test_ec_public.key"); - String keyPem = new String(Files.readAllBytes(path)); - PublicKey publicKey = Crypto.loadPublicKey(keyPem); - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("sys.auth.zts", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - provider.signingKeyResolver.addPublicKey("k0", publicKey); + provider.initialize("sys.auth.zts", "com.yahoo.athenz.instance.provider.impl.InstanceWorkloadIPTokenProvider", null, keystore); InstanceConfirmation tokenConfirmation = new InstanceConfirmation(); tokenConfirmation.setDomain("sports"); @@ -915,135 +564,12 @@ public void testConfirmInstanceEmptyCredentials() throws IOException { try { provider.confirmInstance(confirmation); fail(); - } catch (ResourceException ex) { - assertEquals(ex.getCode(), ResourceException.FORBIDDEN); + } catch (ProviderResourceException ex) { + assertEquals(ex.getCode(), ProviderResourceException.FORBIDDEN); assertTrue(ex.getMessage().contains("Service credentials not provided")); } provider.close(); System.clearProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST); } - - @Test - public void testValidateRegisterTokenNullIssueDate() throws IOException { - - KeyStore keystore = Mockito.mock(KeyStore.class); - Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - - System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api"); - - // get our ec public key - - Path path = Paths.get("./src/test/resources/unit_test_ec_public.key"); - String keyPem = new String(Files.readAllBytes(path)); - PublicKey publicKey = Crypto.loadPublicKey(keyPem); - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("sys.auth.zts", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - provider.signingKeyResolver.addPublicKey("k0", publicKey); - - path = Paths.get("./src/test/resources/unit_test_ec_private.key"); - keyPem = new String(Files.readAllBytes(path)); - PrivateKey privateKey = Crypto.loadPrivateKey(keyPem); - - // first generate token with no issue date - - final String registerToken = Jwts.builder() - .setId("001") - .setSubject("sports.api") - .setIssuer("sys.auth.zts") - .setAudience("sys.auth.zts") - .claim(CLAIM_PROVIDER, "sys.auth.zts") - .claim(CLAIM_DOMAIN, "sports") - .claim(CLAIM_SERVICE, "api") - .claim(CLAIM_INSTANCE_ID, "id001") - .claim(CLAIM_WORKLOAD_IP, "127.0.0.1") - .claim(CLAIM_CLIENT_ID, "user.athenz") - .setHeaderParam(HDR_KEY_ID, "k0") - .setHeaderParam(HDR_TOKEN_TYPE, HDR_TOKEN_JWT) - .signWith(privateKey, SignatureAlgorithm.ES256) - .compact(); - - - // with register instance enabled, this is going to be reject since - // there is no issue date - - StringBuilder errMsg = new StringBuilder(); - assertFalse(provider.validateRegisterToken(registerToken, - "sports", "api", "id001", "127.0.0.1", true, errMsg)); - assertTrue(errMsg.toString().contains("token is already expired, issued at: null")); - - // with refresh option it's going to be skipped - - errMsg.setLength(0); - assertTrue(provider.validateRegisterToken(registerToken, - "sports", "api", "id001", "127.0.0.1", false, errMsg)); - - provider.close(); - System.clearProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST); - } - - @Test - public void testValidateRegisterTokenExpiredIssueDate() throws IOException { - - KeyStore keystore = Mockito.mock(KeyStore.class); - Mockito.when(keystore.getPublicKey("sports", "api", "v0")).thenReturn(servicePublicKeyStringK0); - - System.setProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST, "sports.api"); - - // get our ec public key - - Path path = Paths.get("./src/test/resources/unit_test_ec_public.key"); - String keyPem = new String(Files.readAllBytes(path)); - PublicKey publicKey = Crypto.loadPublicKey(keyPem); - - InstanceWorkloadIPTokenProvider provider = new InstanceWorkloadIPTokenProvider(); - provider.initialize("sys.auth.zts", "com.yahoo.athenz.instance.provider.impl.InstanceZTSProvider", null, keystore); - provider.signingKeyResolver.addPublicKey("k0", publicKey); - - path = Paths.get("./src/test/resources/unit_test_ec_private.key"); - keyPem = new String(Files.readAllBytes(path)); - PrivateKey privateKey = Crypto.loadPrivateKey(keyPem); - - // first generate token with no issue date - - Instant issueTime = Instant.ofEpochMilli(System.currentTimeMillis() - - TimeUnit.MINUTES.toMillis(31)); - Date issueDate = Date.from(issueTime); - - final String registerToken = Jwts.builder() - .setId("001") - .setSubject("sports.api") - .setIssuedAt(issueDate) - .setIssuer("sys.auth.zts") - .setAudience("sys.auth.zts") - .claim(CLAIM_PROVIDER, "sys.auth.zts") - .claim(CLAIM_DOMAIN, "sports") - .claim(CLAIM_SERVICE, "api") - .claim(CLAIM_INSTANCE_ID, "id001") - .claim(CLAIM_WORKLOAD_IP, "127.0.0.1") - .claim(CLAIM_CLIENT_ID, "user.athenz") - .setHeaderParam(HDR_KEY_ID, "k0") - .setHeaderParam(HDR_TOKEN_TYPE, HDR_TOKEN_JWT) - .signWith(privateKey, SignatureAlgorithm.ES256) - .compact(); - - - // with register instance enabled, this is going to be reject since - // there is no issue date - - StringBuilder errMsg = new StringBuilder(); - assertFalse(provider.validateRegisterToken(registerToken, - "sports", "api", "id001", "127.0.0.1", true, errMsg)); - assertTrue(errMsg.toString().contains("token is already expired, issued at: " + issueDate)); - - // with refresh option it's going to be skipped - - errMsg.setLength(0); - assertTrue(provider.validateRegisterToken(registerToken, - "sports", "api", "id001", "127.0.0.1", false, errMsg)); - - provider.close(); - System.clearProperty(InstanceWorkloadIPTokenProvider.ZTS_PROP_PRINCIPAL_LIST); - } -} +} \ No newline at end of file diff --git a/src/test/resources/jwt_jwks.json b/src/test/resources/jwt_jwks.json new file mode 100644 index 0000000..1eaab3d --- /dev/null +++ b/src/test/resources/jwt_jwks.json @@ -0,0 +1,30 @@ +{ + "keys": [ + { + "kty":"RSA", + "kid":"0", + "alg":"RS256", + "use":"sig", + "n":"AMV3cnZXxYJL-A0TYY8Fy245HKSOBCYt9atNAUQVtbEwx9QaZGj8moYIe4nXgx72Ktwg0Gruh8sS7GQLBizCXg7fCk62sDV_MZINnwON9gsKbxxgn9mLFeYSaatUzk-VRphDoHNIBC-qeDtYnZhsHYcV9Jp0GPkLNquhN1TXA7gT", + "e":"AQAB" + }, + { + "kty":"EC", + "kid":"eckey1", + "alg":"ES256", + "use":"sig", + "crv":"P-256", + "x":"AI0x6wEUk5T0hslaT83DNVy5r98XnG7HAjQynjCrcdCe", + "y":"ATdV2ebpefqBli_SXZwvL3-7OiD3MTryGbR-zRSFZ_s=" + }, + { + "kty":"EC", + "kid":"k0", + "alg":"ES256", + "use":"sig", + "crv":"P-256", + "x":"AI0x6wEUk5T0hslaT83DNVy5r98XnG7HAjQynjCrcdCe", + "y":"ATdV2ebpefqBli_SXZwvL3-7OiD3MTryGbR-zRSFZ_s=" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/jwt_jwks_empty.json b/src/test/resources/jwt_jwks_empty.json new file mode 100644 index 0000000..8836e70 --- /dev/null +++ b/src/test/resources/jwt_jwks_empty.json @@ -0,0 +1,4 @@ +{ + "keys": [ + ] +} \ No newline at end of file diff --git a/yq.txt b/yq.txt new file mode 100644 index 0000000..2dbea42 --- /dev/null +++ b/yq.txt @@ -0,0 +1,3 @@ +yq eval -o=json - -pxml <(curl -s https://raw.githubusercontent.com/AthenZ/athenz/refs/heads/master/pom.xml | yq -p xml .project.properties | sort) <(cat pom.xml.template | yq -p xml .project.properties | sort) | \ +yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' - | \ +yq eval -o=xml . -