001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.List; 024 025import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebEndpointDiscoverer; 026import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; 027import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; 028import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; 029import org.springframework.boot.actuate.endpoint.ExposableEndpoint; 030import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; 031import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; 032import org.springframework.boot.actuate.endpoint.web.EndpointMapping; 033import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; 034import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; 035import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier; 036import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; 037import org.springframework.boot.actuate.health.HealthEndpoint; 038import org.springframework.boot.actuate.health.HealthEndpointWebExtension; 039import org.springframework.boot.autoconfigure.AutoConfigureAfter; 040import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 041import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 042import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 043import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; 044import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 045import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 046import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 047import org.springframework.boot.autoconfigure.security.SecurityProperties; 048import org.springframework.boot.cloud.CloudPlatform; 049import org.springframework.boot.web.client.RestTemplateBuilder; 050import org.springframework.context.ApplicationContext; 051import org.springframework.context.annotation.Bean; 052import org.springframework.context.annotation.Configuration; 053import org.springframework.core.annotation.Order; 054import org.springframework.core.env.Environment; 055import org.springframework.http.HttpMethod; 056import org.springframework.security.config.annotation.web.WebSecurityConfigurer; 057import org.springframework.security.config.annotation.web.builders.WebSecurity; 058import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 059import org.springframework.web.cors.CorsConfiguration; 060import org.springframework.web.servlet.DispatcherServlet; 061 062/** 063 * {@link EnableAutoConfiguration Auto-configuration} to expose actuator endpoints for 064 * Cloud Foundry to use. 065 * 066 * @author Madhura Bhave 067 * @since 2.0.0 068 */ 069@Configuration 070@ConditionalOnProperty(prefix = "management.cloudfoundry", name = "enabled", matchIfMissing = true) 071@AutoConfigureAfter({ ServletManagementContextAutoConfiguration.class, 072 HealthEndpointAutoConfiguration.class }) 073@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 074@ConditionalOnClass(DispatcherServlet.class) 075@ConditionalOnBean(DispatcherServlet.class) 076@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) 077public class CloudFoundryActuatorAutoConfiguration { 078 079 private final ApplicationContext applicationContext; 080 081 CloudFoundryActuatorAutoConfiguration(ApplicationContext applicationContext) { 082 this.applicationContext = applicationContext; 083 } 084 085 @Bean 086 @ConditionalOnMissingBean 087 @ConditionalOnEnabledEndpoint 088 @ConditionalOnBean({ HealthEndpoint.class, HealthEndpointWebExtension.class }) 089 public CloudFoundryHealthEndpointWebExtension cloudFoundryHealthEndpointWebExtension( 090 HealthEndpointWebExtension healthEndpointWebExtension) { 091 return new CloudFoundryHealthEndpointWebExtension(healthEndpointWebExtension); 092 } 093 094 @Bean 095 public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( 096 ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, 097 RestTemplateBuilder restTemplateBuilder, 098 ServletEndpointsSupplier servletEndpointsSupplier, 099 ControllerEndpointsSupplier controllerEndpointsSupplier) { 100 CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer( 101 this.applicationContext, parameterMapper, endpointMediaTypes, null, 102 Collections.emptyList(), Collections.emptyList()); 103 CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor( 104 restTemplateBuilder, this.applicationContext.getEnvironment()); 105 Collection<ExposableWebEndpoint> webEndpoints = discoverer.getEndpoints(); 106 List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>(); 107 allEndpoints.addAll(webEndpoints); 108 allEndpoints.addAll(servletEndpointsSupplier.getEndpoints()); 109 allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints()); 110 return new CloudFoundryWebEndpointServletHandlerMapping( 111 new EndpointMapping("/cloudfoundryapplication"), webEndpoints, 112 endpointMediaTypes, getCorsConfiguration(), securityInterceptor, 113 new EndpointLinksResolver(allEndpoints)); 114 } 115 116 private CloudFoundrySecurityInterceptor getSecurityInterceptor( 117 RestTemplateBuilder restTemplateBuilder, Environment environment) { 118 CloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService( 119 restTemplateBuilder, environment); 120 TokenValidator tokenValidator = new TokenValidator(cloudfoundrySecurityService); 121 return new CloudFoundrySecurityInterceptor(tokenValidator, 122 cloudfoundrySecurityService, 123 environment.getProperty("vcap.application.application_id")); 124 } 125 126 private CloudFoundrySecurityService getCloudFoundrySecurityService( 127 RestTemplateBuilder restTemplateBuilder, Environment environment) { 128 String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); 129 boolean skipSslValidation = environment.getProperty( 130 "management.cloudfoundry.skip-ssl-validation", Boolean.class, false); 131 return (cloudControllerUrl != null) ? new CloudFoundrySecurityService( 132 restTemplateBuilder, cloudControllerUrl, skipSslValidation) : null; 133 } 134 135 private CorsConfiguration getCorsConfiguration() { 136 CorsConfiguration corsConfiguration = new CorsConfiguration(); 137 corsConfiguration.addAllowedOrigin(CorsConfiguration.ALL); 138 corsConfiguration.setAllowedMethods( 139 Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name())); 140 corsConfiguration.setAllowedHeaders( 141 Arrays.asList("Authorization", "X-Cf-App-Instance", "Content-Type")); 142 return corsConfiguration; 143 } 144 145 /** 146 * {@link WebSecurityConfigurer} to tell Spring Security to ignore cloudfoundry 147 * specific paths. The Cloud foundry endpoints are protected by their own security 148 * interceptor. 149 */ 150 @ConditionalOnClass(WebSecurity.class) 151 @Order(SecurityProperties.IGNORED_ORDER) 152 @Configuration 153 public static class IgnoredPathsWebSecurityConfigurer 154 implements WebSecurityConfigurer<WebSecurity> { 155 156 @Override 157 public void init(WebSecurity builder) throws Exception { 158 builder.ignoring().requestMatchers( 159 new AntPathRequestMatcher("/cloudfoundryapplication/**")); 160 } 161 162 @Override 163 public void configure(WebSecurity builder) throws Exception { 164 } 165 166 } 167 168}