| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.knox.gateway.service.metadata; |
| |
| import static javax.ws.rs.core.MediaType.APPLICATION_JSON; |
| import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM; |
| import static javax.ws.rs.core.MediaType.APPLICATION_XML; |
| |
| import java.io.IOException; |
| import java.nio.file.Paths; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.CertificateException; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Locale; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import javax.inject.Singleton; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.ws.rs.DefaultValue; |
| import javax.ws.rs.GET; |
| import javax.ws.rs.Path; |
| import javax.ws.rs.PathParam; |
| import javax.ws.rs.Produces; |
| import javax.ws.rs.QueryParam; |
| import javax.ws.rs.core.Context; |
| import javax.ws.rs.core.Response; |
| import javax.ws.rs.core.Response.ResponseBuilder; |
| import javax.ws.rs.core.Response.Status; |
| |
| import org.apache.knox.gateway.config.GatewayConfig; |
| import org.apache.knox.gateway.i18n.messages.MessagesFactory; |
| import org.apache.knox.gateway.service.definition.Metadata; |
| import org.apache.knox.gateway.service.definition.ServiceDefinitionPair; |
| import org.apache.knox.gateway.services.GatewayServices; |
| import org.apache.knox.gateway.services.ServerInfoService; |
| import org.apache.knox.gateway.services.ServiceType; |
| import org.apache.knox.gateway.services.registry.ServiceDefinitionRegistry; |
| import org.apache.knox.gateway.services.security.KeystoreService; |
| import org.apache.knox.gateway.services.security.KeystoreServiceException; |
| import org.apache.knox.gateway.services.topology.TopologyService; |
| import org.apache.knox.gateway.topology.Service; |
| import org.apache.knox.gateway.topology.Topology; |
| import org.apache.knox.gateway.util.X509CertificateUtil; |
| |
| @Singleton |
| @Path("/api/v1/metadata") |
| public class KnoxMetadataResource { |
| private static final MetadataServiceMessages LOG = MessagesFactory.get(MetadataServiceMessages.class); |
| private static final String SNAPSHOT_VERSION_POSTFIX = "-SNAPSHOT"; |
| private static final Set<String> UNREAL_SERVICES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("NAMENODE", "JOBTRACKER"))); |
| |
| private Set<String> pinnedTopologies; |
| private java.nio.file.Path pemFilePath; |
| private java.nio.file.Path jksFilePath; |
| |
| @Context |
| private HttpServletRequest request; |
| |
| @GET |
| @Produces({ APPLICATION_JSON, APPLICATION_XML }) |
| @Path("info") |
| public GeneralProxyInformation getGeneralProxyInformation() { |
| final GeneralProxyInformation proxyInfo = new GeneralProxyInformation(); |
| final GatewayServices gatewayServices = (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); |
| if (gatewayServices != null) { |
| final ServerInfoService serviceInfoService = gatewayServices.getService(ServiceType.SERVER_INFO_SERVICE); |
| final String versionInfo = serviceInfoService.getBuildVersion() + " (hash=" + serviceInfoService.getBuildHash() + ")"; |
| proxyInfo.setVersion(versionInfo); |
| proxyInfo.setAdminApiBookUrl( |
| String.format(Locale.ROOT, "https://knox.apache.org/books/knox-%s/user-guide.html#Admin+API", getAdminApiBookVersion(serviceInfoService.getBuildVersion()))); |
| final GatewayConfig config = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE); |
| proxyInfo.setAdminUiUrl(getBaseGatewayUrl(config) + "/manager/admin-ui/"); |
| } |
| |
| return proxyInfo; |
| } |
| |
| private String getAdminApiBookVersion(String buildVersion) { |
| return buildVersion.replaceAll(SNAPSHOT_VERSION_POSTFIX, "").replaceAll("\\.", "-"); |
| } |
| |
| @GET |
| @Produces(APPLICATION_OCTET_STREAM) |
| @Path("publicCert") |
| public Response getPublicCertification(@QueryParam("type") @DefaultValue("pem") String certType) { |
| final GatewayServices gatewayServices = (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); |
| if (gatewayServices != null) { |
| final GatewayConfig config = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE); |
| final Certificate certificate = getPublicCertificate(gatewayServices, config); |
| if (certificate != null) { |
| if ("pem".equals(certType)) { |
| generateCertificatePem(certificate, config); |
| return generateSuccessFileDownloadResponse(pemFilePath); |
| } else if ("jks".equals(certType)) { |
| generateCertificateJks(certificate, config); |
| return generateSuccessFileDownloadResponse(jksFilePath); |
| } else { |
| return generateFailureFileDownloadResponse(Status.BAD_REQUEST, "Invalid certification type provided!"); |
| } |
| } |
| } |
| return generateFailureFileDownloadResponse(Status.SERVICE_UNAVAILABLE, "Could not generate public certificate"); |
| } |
| |
| private Response generateSuccessFileDownloadResponse(java.nio.file.Path publicCertFilePath) { |
| final ResponseBuilder responseBuilder = Response.ok(publicCertFilePath.toFile()); |
| responseBuilder.header("Content-Disposition", "attachment;filename=" + publicCertFilePath.getFileName().toString()); |
| return responseBuilder.build(); |
| } |
| |
| private Response generateFailureFileDownloadResponse(Status status, String errorMessage) { |
| final ResponseBuilder responseBuilder = Response.status(status); |
| responseBuilder.entity(errorMessage); |
| return responseBuilder.build(); |
| } |
| |
| private Certificate getPublicCertificate(GatewayServices gatewayServices, GatewayConfig config) { |
| try { |
| final KeystoreService keystoreService = gatewayServices.getService(ServiceType.KEYSTORE_SERVICE); |
| return keystoreService.getKeystoreForGateway().getCertificate(config.getIdentityKeyAlias()); |
| } catch (KeyStoreException | KeystoreServiceException e) { |
| LOG.failedToFetchPublicCert(e.getMessage(), e); |
| return null; |
| } |
| } |
| |
| private void generateCertificatePem(Certificate certificate, GatewayConfig gatewayConfig) { |
| try { |
| if (pemFilePath == null || !pemFilePath.toFile().exists()) { |
| pemFilePath = Paths.get(gatewayConfig.getGatewaySecurityDir(), "gateway-client-trust.pem"); |
| X509CertificateUtil.writeCertificateToFile(certificate, pemFilePath.toFile()); |
| } |
| } catch (CertificateEncodingException | IOException e) { |
| LOG.failedToGeneratePublicCert("PEM", e.getMessage(), e); |
| } |
| } |
| |
| private void generateCertificateJks(Certificate certificate, GatewayConfig gatewayConfig) { |
| try { |
| if (jksFilePath == null || !jksFilePath.toFile().exists()) { |
| jksFilePath = Paths.get(gatewayConfig.getGatewaySecurityDir(), "gateway-client-trust.jks"); |
| X509CertificateUtil.writeCertificateToJks(certificate, jksFilePath.toFile()); |
| } |
| } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) { |
| LOG.failedToGeneratePublicCert("JKS", e.getMessage(), e); |
| } |
| } |
| |
| private String getBaseGatewayUrl(GatewayConfig config) { |
| return request.getRequestURL().substring(0, request.getRequestURL().length() - request.getRequestURI().length()) + "/" + config.getGatewayPath(); |
| } |
| |
| @GET |
| @Produces({ APPLICATION_XML, APPLICATION_JSON }) |
| @Path("topologies") |
| public TopologyInformationWrapper getTopologies() { |
| return getTopologies(null); |
| } |
| |
| @GET |
| @Produces({ APPLICATION_XML, APPLICATION_JSON }) |
| @Path("topologies/{name}") |
| public TopologyInformationWrapper getTopology(@PathParam("name") String topologyName) { |
| return getTopologies(topologyName); |
| } |
| |
| private TopologyInformationWrapper getTopologies(String topologyName) { |
| final TopologyInformationWrapper topologies = new TopologyInformationWrapper(); |
| final GatewayServices gatewayServices = (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); |
| final GatewayConfig config = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE); |
| final ServiceDefinitionRegistry serviceDefinitionRegistry = gatewayServices.getService(ServiceType.SERVICE_DEFINITION_REGISTRY); |
| final Set<String> hiddenTopologies = config.getHiddenTopologiesOnHomepage(); |
| if (gatewayServices != null) { |
| final TopologyService topologyService = gatewayServices.getService(ServiceType.TOPOLOGY_SERVICE); |
| for (Topology topology : topologyService.getTopologies()) { |
| if (!hiddenTopologies.contains(topology.getName()) && (topologyName == null || topology.getName().equalsIgnoreCase(topologyName))) { |
| Set<ServiceModel> apiServices = new HashSet<>(); |
| Set<ServiceModel> uiServices = new HashSet<>(); |
| topology.getServices().stream().filter(service -> !UNREAL_SERVICES.contains(service.getRole())).forEach(service -> { |
| service.getUrls().forEach(serviceUrl -> { |
| ServiceModel serviceModel = getServiceModel(request, config.getGatewayPath(), topology.getName(), service, getServiceMetadata(serviceDefinitionRegistry, service), |
| serviceUrl); |
| if (ServiceModel.Type.UI == serviceModel.getType()) { |
| uiServices.add(serviceModel); |
| } else if (ServiceModel.Type.API_AND_UI == serviceModel.getType()) { |
| uiServices.add(serviceModel); |
| apiServices.add(serviceModel); |
| } else { |
| apiServices.add(serviceModel); |
| } |
| }); |
| }); |
| topologies.addTopology(topology.getName(), isPinnedTopology(topology.getName(), config), new TreeSet<>(apiServices), new TreeSet<>(uiServices)); |
| } |
| } |
| } |
| return topologies; |
| } |
| |
| boolean isPinnedTopology(String topologyName, GatewayConfig config) { |
| if (pinnedTopologies == null) { |
| pinnedTopologies = config.getPinnedTopologiesOnHomepage(); |
| } |
| return pinnedTopologies.contains(topologyName); |
| } |
| |
| private Metadata getServiceMetadata(ServiceDefinitionRegistry serviceDefinitionRegistry, Service service) { |
| final Optional<ServiceDefinitionPair> serviceDefinition = serviceDefinitionRegistry.getServiceDefinitions().stream() |
| .filter(serviceDefinitionPair -> serviceDefinitionPair.getService().getRole().equalsIgnoreCase(service.getRole())) |
| .filter(serviceDefinitionPair -> service.getVersion() == null || service.getVersion().toString().equalsIgnoreCase(serviceDefinitionPair.getService().getVersion())) |
| .findFirst(); |
| return serviceDefinition.isPresent() ? serviceDefinition.get().getService().getMetadata() : null; |
| } |
| |
| private ServiceModel getServiceModel(HttpServletRequest request, String gatewayPath, String topologyName, Service service, Metadata serviceMetadata, String serviceUrl) { |
| final ServiceModel serviceModel = new ServiceModel(); |
| serviceModel.setRequest(request); |
| serviceModel.setGatewayPath(gatewayPath); |
| serviceModel.setTopologyName(topologyName); |
| serviceModel.setService(service); |
| serviceModel.setServiceMetadata(serviceMetadata); |
| serviceModel.setServiceUrl(serviceUrl); |
| return serviceModel; |
| } |
| |
| } |