diff options
7 files changed, 111 insertions, 12 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceConfirmationResource.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceConfirmationResource.java index 4c71cb7855d..7a4e82d134b 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceConfirmationResource.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceConfirmationResource.java @@ -16,7 +16,7 @@ import java.util.logging.Logger; /** * @author bjorncs */ -@Path("/{path: instance|refresh}") +@Path("/instance") public class InstanceConfirmationResource { private static final Logger log = Logger.getLogger(InstanceConfirmationResource.class.getName()); @@ -32,6 +32,7 @@ public class InstanceConfirmationResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public InstanceConfirmation confirmInstance(InstanceConfirmation instanceConfirmation) { + log.log(LogLevel.DEBUG, instanceConfirmation.toString()); if (!instanceValidator.isValidInstance(instanceConfirmation)) { log.log(LogLevel.ERROR, "Invalid instance: " + instanceConfirmation); throw new ForbiddenException("Instance is invalid"); diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceRefreshResource.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceRefreshResource.java new file mode 100644 index 00000000000..53972a39c97 --- /dev/null +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceRefreshResource.java @@ -0,0 +1,44 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.athenz.instanceproviderservice.instanceconfirmation; + +import com.google.inject.Inject; +import com.yahoo.container.jaxrs.annotation.Component; +import com.yahoo.log.LogLevel; + +import javax.ws.rs.Consumes; +import javax.ws.rs.ForbiddenException; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.util.logging.Logger; + +/** + * ZTS calls this resource when it's requested to refresh an instance certificate + * + * @author bjorncs + */ +@Path("/refresh") +public class InstanceRefreshResource { + + private static final Logger log = Logger.getLogger(InstanceRefreshResource.class.getName()); + + private final InstanceValidator instanceValidator; + + @Inject + public InstanceRefreshResource(@Component InstanceValidator instanceValidator) { + this.instanceValidator = instanceValidator; + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public InstanceConfirmation confirmInstanceRefresh(InstanceConfirmation instanceConfirmation) { + log.log(LogLevel.DEBUG, instanceConfirmation.toString()); + if (!instanceValidator.isValidRefresh(instanceConfirmation)) { + log.log(LogLevel.ERROR, "Invalid instance refresh: " + instanceConfirmation); + throw new ForbiddenException("Instance is invalid"); + } + return instanceConfirmation; + } +} diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java index b75f7d05394..dcaf50c1c04 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/instanceconfirmation/InstanceValidator.java @@ -61,6 +61,17 @@ public class InstanceValidator { return false; } + // TODO Add actual validation. Cannot reuse isValidInstance as identity document is not part of the refresh request. + // We'll have to perform some validation on the instance id and other fields of the attribute map. + // Separate between tenant and node certificate as well. + public boolean isValidRefresh(InstanceConfirmation confirmation) { + log.log(LogLevel.INFO, () -> String.format("Accepting refresh for instance with identity '%s', provider '%s', instanceId '%s'.", + new AthenzService(confirmation.domain, confirmation.service).getFullName(), + confirmation.provider, + confirmation.attributes.get("sanDNS").toString())); + return true; + } + // If/when we dont care about logging exactly whats wrong, this can be simplified // TODO Use identity type to determine if this check should be performed boolean isSameIdentityAsInServicesXml(ApplicationId applicationId, String domain, String service) { diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java index ff85c49bb13..a8403b8b10d 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java @@ -7,6 +7,7 @@ import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient; import com.yahoo.vespa.athenz.client.zts.InstanceIdentity; import com.yahoo.vespa.athenz.client.zts.ZtsClient; +import com.yahoo.vespa.athenz.client.zts.ZtsClientException; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper; import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocumentClient; @@ -142,6 +143,8 @@ public class AthenzCredentialsMaintainer { log.info(String.format("Deleted private key file (path=%s)", privateKeyFile)); if (Files.deleteIfExists(certificateFile)) log.info(String.format("Deleted certificate file (path=%s)", certificateFile)); + if (Files.deleteIfExists(identityDocumentFile)) + log.info(String.format("Deleted identity document file (path=%s)", certificateFile)); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -201,6 +204,12 @@ public class AthenzCredentialsMaintainer { csr); writePrivateKeyAndCertificate(keyPair.getPrivate(), instanceIdentity.certificate()); log.info("Instance successfully refreshed and credentials written to file"); + } catch (ZtsClientException e) { + // TODO Find out why certificate was revoked and hopefully remove this workaround + if (e.getErrorCode() == 403 && e.getDescription().startsWith("Certificate revoked")) { + log.error("Certificate cannot be refreshed as it is revoked by ZTS - re-registering the instance now", e); + registerIdentity(); + } } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java index 8c67c3386b7..8a94518cee7 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java @@ -10,10 +10,11 @@ import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.athenz.api.ZToken; +import com.yahoo.vespa.athenz.client.zts.bindings.ErrorResponseEntity; +import com.yahoo.vespa.athenz.client.zts.bindings.IdentityRefreshRequestEntity; import com.yahoo.vespa.athenz.client.zts.bindings.IdentityResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceIdentityCredentials; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceRefreshInformation; -import com.yahoo.vespa.athenz.client.zts.bindings.IdentityRefreshRequestEntity; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceRegisterInformation; import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateRequestEntity; import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateResponseEntity; @@ -33,7 +34,6 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.util.EntityUtils; import org.eclipse.jetty.http.HttpStatus; import javax.net.ssl.SSLContext; @@ -48,7 +48,6 @@ import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; import static com.yahoo.vespa.athenz.tls.SignatureAlgorithm.SHA256_WITH_RSA; import static com.yahoo.vespa.athenz.tls.SubjectAlternativeName.Type.DNS_NAME; @@ -237,10 +236,8 @@ public class DefaultZtsClient implements ZtsClient { if (HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) { return objectMapper.readValue(response.getEntity().getContent(), entityType); } else { - String message = EntityUtils.toString(response.getEntity()); - throw new ZtsClientException( - String.format("Unable to get identity. http code/message: %d/%s", - response.getStatusLine().getStatusCode(), message)); + ErrorResponseEntity errorEntity = objectMapper.readValue(response.getEntity().getContent(), ErrorResponseEntity.class); + throw new ZtsClientException(errorEntity.code, errorEntity.description); } } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClientException.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClientException.java index 3d3696ad870..0b0d6914fea 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClientException.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClientException.java @@ -8,11 +8,25 @@ package com.yahoo.vespa.athenz.client.zts; */ public class ZtsClientException extends RuntimeException { - public ZtsClientException(String message) { - super(message); + private final int errorCode; + private final String description; + + public ZtsClientException(int errorCode, String description) { + super(createMessage(errorCode, description)); + this.errorCode = errorCode; + this.description = description; + } + + public int getErrorCode() { + return errorCode; } - public ZtsClientException(String message, Throwable cause) { - super(message, cause); + public String getDescription() { + return description; } + + private static String createMessage(int code, String description) { + return String.format("Received error from ZTS: code=%d, message=\"%s\"", code, description); + } + } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/ErrorResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/ErrorResponseEntity.java new file mode 100644 index 00000000000..431af084f9f --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/ErrorResponseEntity.java @@ -0,0 +1,23 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.athenz.client.zts.bindings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author bjorncs + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ErrorResponseEntity { + + public final int code; + public final String description; + + @JsonCreator + public ErrorResponseEntity(@JsonProperty("code") int code, + @JsonProperty("message") String description) { + this.code = code; + this.description = description; + } +} |