| /* |
| * 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.ambari.logsearch.conf; |
| |
| import static javax.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE; |
| import static org.apache.ambari.logsearch.common.LogSearchConstants.LOGSEARCH_SESSION_ID; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.ambari.logsearch.common.LogSearchLdapAuthorityMapper; |
| import org.apache.ambari.logsearch.common.StatusMessage; |
| import org.apache.ambari.logsearch.conf.global.LogLevelFilterManagerState; |
| import org.apache.ambari.logsearch.conf.global.LogSearchConfigState; |
| import org.apache.ambari.logsearch.conf.global.SolrCollectionState; |
| import org.apache.ambari.logsearch.dao.RoleDao; |
| import org.apache.ambari.logsearch.web.authenticate.LogsearchAuthFailureHandler; |
| import org.apache.ambari.logsearch.web.authenticate.LogsearchAuthSuccessHandler; |
| import org.apache.ambari.logsearch.web.authenticate.LogsearchLogoutSuccessHandler; |
| import org.apache.ambari.logsearch.web.filters.ConfigStateProvider; |
| import org.apache.ambari.logsearch.web.filters.GlobalStateProvider; |
| import org.apache.ambari.logsearch.web.filters.LogsearchAuthenticationEntryPoint; |
| import org.apache.ambari.logsearch.web.filters.LogsearchCorsFilter; |
| import org.apache.ambari.logsearch.web.filters.LogsearchFilter; |
| import org.apache.ambari.logsearch.web.filters.LogsearchJWTFilter; |
| import org.apache.ambari.logsearch.web.filters.LogsearchKRBAuthenticationFilter; |
| import org.apache.ambari.logsearch.web.filters.LogsearchSecurityContextFormationFilter; |
| import org.apache.ambari.logsearch.web.filters.LogsearchTrustedProxyFilter; |
| import org.apache.ambari.logsearch.web.filters.LogsearchUsernamePasswordAuthenticationFilter; |
| import org.apache.ambari.logsearch.web.security.LogsearchAuthenticationProvider; |
| import org.apache.ambari.logsearch.web.security.LogsearchLdapAuthenticationProvider; |
| import org.apache.commons.lang.StringUtils; |
| import org.springframework.context.annotation.Bean; |
| import org.springframework.context.annotation.Configuration; |
| import org.springframework.ldap.core.support.LdapContextSource; |
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
| import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
| import org.springframework.security.ldap.authentication.BindAuthenticator; |
| import org.springframework.security.ldap.authentication.NullLdapAuthoritiesPopulator; |
| import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; |
| import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; |
| import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; |
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
| import org.springframework.security.crypto.password.PasswordEncoder; |
| import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; |
| import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; |
| import org.springframework.security.web.header.Header; |
| import org.springframework.security.web.header.HeaderWriter; |
| import org.springframework.security.web.header.writers.HstsHeaderWriter; |
| import org.springframework.security.web.header.writers.StaticHeadersWriter; |
| import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter; |
| import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; |
| import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; |
| import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
| import org.springframework.security.web.util.matcher.OrRequestMatcher; |
| import org.springframework.security.web.util.matcher.RequestMatcher; |
| |
| import com.google.common.collect.Lists; |
| |
| @Configuration |
| @EnableWebSecurity |
| public class SecurityConfig extends WebSecurityConfigurerAdapter { |
| |
| @Inject |
| private AuthPropsConfig authPropsConfig; |
| |
| @Inject |
| private LogSearchHttpHeaderConfig logSearchHttpHeaderConfig; |
| |
| @Inject |
| private LogSearchHttpConfig logSearchHttpConfig; |
| |
| @Inject |
| private SolrServiceLogPropsConfig solrServiceLogPropsConfig; |
| |
| @Inject |
| private SolrAuditLogPropsConfig solrAuditLogPropsConfig; |
| |
| @Inject |
| private SolrMetadataPropsConfig solrEventHistoryPropsConfig; |
| |
| @Inject |
| @Named("solrServiceLogsState") |
| private SolrCollectionState solrServiceLogsState; |
| |
| @Inject |
| @Named("solrAuditLogsState") |
| private SolrCollectionState solrAuditLogsState; |
| |
| @Inject |
| @Named("solrMetadataState") |
| private SolrCollectionState solrMetadataState; |
| |
| @Inject |
| @Named("logLevelFilterManagerState") |
| private LogLevelFilterManagerState logLevelFilterManagerState; |
| |
| @Inject |
| private LogSearchConfigState logSearchConfigState; |
| |
| @Inject |
| private LogSearchConfigApiConfig logSearchConfigApiConfig; |
| |
| @Inject |
| private RoleDao roleDao; |
| |
| @Override |
| protected void configure(HttpSecurity http) throws Exception { |
| http |
| .headers() |
| .addHeaderWriter( |
| new LogSearchCompositeHeaderWriter("https".equals(logSearchHttpConfig.getProtocol()), |
| new XXssProtectionHeaderWriter(), |
| new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY), |
| new XContentTypeOptionsHeaderWriter(), |
| new StaticHeadersWriter("Pragma", "no-cache"), |
| new StaticHeadersWriter("Cache-Control", "no-store"))) |
| .and() |
| .csrf().disable() |
| .authorizeRequests() |
| .requestMatchers(requestMatcher()) |
| .permitAll() |
| .antMatchers("/**") |
| .hasRole("USER") |
| .and() |
| .authenticationProvider(logsearchAuthenticationProvider()) |
| .httpBasic() |
| .authenticationEntryPoint(logsearchAuthenticationEntryPoint()) |
| .and() |
| .addFilterBefore(logsearchTrustedProxyFilter(), BasicAuthenticationFilter.class) |
| .addFilterAfter(logsearchKRBAuthenticationFilter(), LogsearchTrustedProxyFilter.class) |
| .addFilterBefore(logsearchUsernamePasswordAuthenticationFilter(), LogsearchKRBAuthenticationFilter.class) |
| .addFilterAfter(securityContextFormationFilter(), FilterSecurityInterceptor.class) |
| .addFilterAfter(logsearchMetadataFilter(), LogsearchSecurityContextFormationFilter.class) |
| .addFilterAfter(logsearchAuditLogFilter(), LogsearchSecurityContextFormationFilter.class) |
| .addFilterAfter(logsearchServiceLogFilter(), LogsearchSecurityContextFormationFilter.class) |
| .addFilterAfter(logSearchConfigStateFilter(), LogsearchSecurityContextFormationFilter.class) |
| .addFilterBefore(logsearchCorsFilter(), LogsearchSecurityContextFormationFilter.class) |
| .addFilterBefore(logsearchJwtFilter(), LogsearchSecurityContextFormationFilter.class) |
| .logout() |
| .logoutUrl("/logout") |
| .deleteCookies(getCookies()) |
| .logoutSuccessHandler(new LogsearchLogoutSuccessHandler()); |
| |
| if ((logSearchConfigApiConfig.isSolrFilterStorage() || logSearchConfigApiConfig.isZkFilterStorage()) |
| && !logSearchConfigApiConfig.isConfigApiEnabled()) |
| http.addFilterAfter(logSearchLogLevelFilterManagerFilter(), LogsearchSecurityContextFormationFilter.class); |
| } |
| |
| @Bean |
| public LdapContextSource ldapContextSource() { |
| if (authPropsConfig.isAuthLdapEnabled()) { |
| final LdapContextSource ldapContextSource = new LdapContextSource(); |
| ldapContextSource.setUrl(authPropsConfig.getLdapAuthConfig().getLdapUrl()); |
| ldapContextSource.setBase(authPropsConfig.getLdapAuthConfig().getLdapBaseDn()); |
| if (StringUtils.isNotBlank(authPropsConfig.getLdapAuthConfig().getLdapManagerDn())) { |
| ldapContextSource.setUserDn(authPropsConfig.getLdapAuthConfig().getLdapManagerDn()); |
| } |
| if (StringUtils.isNotBlank(authPropsConfig.getLdapAuthConfig().getLdapManagerPassword())) { |
| ldapContextSource.setPassword(authPropsConfig.getLdapAuthConfig().getLdapManagerPassword()); |
| } |
| ldapContextSource.setReferral(authPropsConfig.getLdapAuthConfig().getReferralMethod()); |
| ldapContextSource.setAnonymousReadOnly(true); |
| ldapContextSource.afterPropertiesSet(); |
| return ldapContextSource; |
| } |
| return null; |
| } |
| |
| @Bean |
| public BindAuthenticator bindAuthenticator() { |
| if (authPropsConfig.isAuthLdapEnabled()) { |
| final BindAuthenticator bindAuthenticator = new BindAuthenticator(ldapContextSource()); |
| if (StringUtils.isNotBlank(authPropsConfig.getLdapAuthConfig().getLdapUserDnPattern())) { |
| bindAuthenticator.setUserDnPatterns(new String[]{authPropsConfig.getLdapAuthConfig().getLdapUserDnPattern()}); |
| } |
| if (StringUtils.isNotBlank(authPropsConfig.getLdapAuthConfig().getLdapUserSearchFilter())) { |
| bindAuthenticator.setUserSearch(new FilterBasedLdapUserSearch( |
| authPropsConfig.getLdapAuthConfig().getLdapUserSearchBase(), |
| authPropsConfig.getLdapAuthConfig().getLdapUserSearchFilter(), |
| ldapContextSource())); |
| } |
| |
| return bindAuthenticator; |
| } |
| return null; |
| } |
| |
| @Bean |
| public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() { |
| if (authPropsConfig.isAuthLdapEnabled() || StringUtils.isNotBlank(authPropsConfig.getLdapAuthConfig().getLdapGroupSearchBase())) { |
| final DefaultLdapAuthoritiesPopulator ldapAuthoritiesPopulator = |
| new DefaultLdapAuthoritiesPopulator(ldapContextSource(), authPropsConfig.getLdapAuthConfig().getLdapGroupSearchBase()); |
| ldapAuthoritiesPopulator.setGroupSearchFilter(authPropsConfig.getLdapAuthConfig().getLdapGroupSearchFilter()); |
| ldapAuthoritiesPopulator.setGroupRoleAttribute(authPropsConfig.getLdapAuthConfig().getLdapGroupRoleAttribute()); |
| ldapAuthoritiesPopulator.setSearchSubtree(true); |
| ldapAuthoritiesPopulator.setConvertToUpperCase(true); |
| return ldapAuthoritiesPopulator; |
| } |
| return new NullLdapAuthoritiesPopulator(); |
| } |
| |
| @Bean |
| public LogsearchLdapAuthenticationProvider ldapAuthenticationProvider() { |
| if (authPropsConfig.isAuthLdapEnabled()) { |
| LogsearchLdapAuthenticationProvider provider = new LogsearchLdapAuthenticationProvider(bindAuthenticator(), ldapAuthoritiesPopulator()); |
| provider.setAuthoritiesMapper(new LogSearchLdapAuthorityMapper(authPropsConfig.getLdapAuthConfig().getLdapGroupRoleMap())); |
| return provider; |
| } |
| return null; |
| } |
| |
| @Bean |
| public LogsearchCorsFilter logsearchCorsFilter() { |
| return new LogsearchCorsFilter(logSearchHttpHeaderConfig); |
| } |
| |
| @Bean |
| public LogsearchSecurityContextFormationFilter securityContextFormationFilter() { |
| return new LogsearchSecurityContextFormationFilter(); |
| } |
| |
| @Bean |
| public LogsearchKRBAuthenticationFilter logsearchKRBAuthenticationFilter() { |
| return new LogsearchKRBAuthenticationFilter(requestMatcher()); |
| } |
| |
| @Bean |
| public LogsearchAuthenticationProvider logsearchAuthenticationProvider() { |
| return new LogsearchAuthenticationProvider(); |
| } |
| |
| @Bean |
| public LogsearchTrustedProxyFilter logsearchTrustedProxyFilter() throws Exception { |
| LogsearchTrustedProxyFilter filter = new LogsearchTrustedProxyFilter(requestMatcher(), authPropsConfig); |
| filter.setAuthenticationManager(authenticationManagerBean()); |
| return filter; |
| } |
| |
| @Bean |
| public LogsearchJWTFilter logsearchJwtFilter() throws Exception { |
| LogsearchJWTFilter filter = new LogsearchJWTFilter(requestMatcher(), authPropsConfig, roleDao); |
| filter.setAuthenticationManager(authenticationManagerBean()); |
| filter.setAuthenticationSuccessHandler(new LogsearchAuthSuccessHandler()); |
| filter.setAuthenticationFailureHandler(new LogsearchAuthFailureHandler()); |
| return filter; |
| } |
| |
| @Bean |
| public LogsearchAuthenticationEntryPoint logsearchAuthenticationEntryPoint() { |
| LogsearchAuthenticationEntryPoint entryPoint = new LogsearchAuthenticationEntryPoint("/login", authPropsConfig); |
| entryPoint.setForceHttps(false); |
| entryPoint.setUseForward(authPropsConfig.isRedirectForward()); |
| return entryPoint; |
| } |
| |
| @Bean |
| public LogsearchUsernamePasswordAuthenticationFilter logsearchUsernamePasswordAuthenticationFilter() throws Exception { |
| LogsearchUsernamePasswordAuthenticationFilter filter = new LogsearchUsernamePasswordAuthenticationFilter(); |
| filter.setAuthenticationSuccessHandler(new LogsearchAuthSuccessHandler()); |
| filter.setAuthenticationFailureHandler(new LogsearchAuthFailureHandler()); |
| filter.setAuthenticationManager(authenticationManagerBean()); |
| return filter; |
| } |
| |
| @Bean |
| public PasswordEncoder passwordEncoder() { |
| return new BCryptPasswordEncoder(); |
| } |
| |
| private LogsearchFilter logsearchServiceLogFilter() { |
| return new LogsearchFilter(serviceLogsRequestMatcher(), new GlobalStateProvider(solrServiceLogsState, solrServiceLogPropsConfig)); |
| } |
| |
| private LogsearchFilter logsearchAuditLogFilter() { |
| return new LogsearchFilter(auditLogsRequestMatcher(), new GlobalStateProvider(solrAuditLogsState, solrAuditLogPropsConfig)); |
| } |
| |
| private LogsearchFilter logsearchMetadataFilter() { |
| return new LogsearchFilter(metadataRequestMatcher(), new GlobalStateProvider(solrMetadataState, solrEventHistoryPropsConfig)); |
| } |
| |
| private LogsearchFilter logSearchConfigStateFilter() { |
| RequestMatcher requestMatcher; |
| if (logSearchConfigApiConfig.isSolrFilterStorage() || logSearchConfigApiConfig.isZkFilterStorage()) { |
| requestMatcher = shipperConfigInputRequestMatcher(); |
| } else { |
| requestMatcher = logsearchConfigRequestMatcher(); |
| } |
| |
| return new LogsearchFilter(requestMatcher, new ConfigStateProvider(logSearchConfigState, logSearchConfigApiConfig.isConfigApiEnabled())); |
| } |
| |
| private LogsearchFilter logSearchLogLevelFilterManagerFilter() { |
| return new LogsearchFilter(logLevelFilterRequestMatcher(), requestUri -> |
| logLevelFilterManagerState.isLogLevelFilterManagerIsReady() ? null : StatusMessage.with(SERVICE_UNAVAILABLE, "Solr log level filter manager is not available")); |
| } |
| |
| @Bean |
| public RequestMatcher requestMatcher() { |
| List<RequestMatcher> matchers = Lists.newArrayList(); |
| matchers.add(new AntPathRequestMatcher("/docs/**")); |
| matchers.add(new AntPathRequestMatcher("/swagger-ui/**")); |
| matchers.add(new AntPathRequestMatcher("/swagger.html")); |
| if (!authPropsConfig.isAuthJwtEnabled()) { |
| matchers.add(new AntPathRequestMatcher("/")); |
| } |
| matchers.add(new AntPathRequestMatcher("/login")); |
| matchers.add(new AntPathRequestMatcher("/logout")); |
| matchers.add(new AntPathRequestMatcher("/resources/**")); |
| matchers.add(new AntPathRequestMatcher("/index.html")); |
| matchers.add(new AntPathRequestMatcher("/favicon.ico")); |
| matchers.add(new AntPathRequestMatcher("/assets/**")); |
| matchers.add(new AntPathRequestMatcher("/templates/**")); |
| matchers.add(new AntPathRequestMatcher("/api/v1/info/**")); |
| matchers.add(new AntPathRequestMatcher("/api/v1/swagger.json")); |
| matchers.add(new AntPathRequestMatcher("/api/v1/swagger.yaml")); |
| return new OrRequestMatcher(matchers); |
| } |
| |
| public RequestMatcher serviceLogsRequestMatcher() { |
| return new AntPathRequestMatcher("/api/v1/service/logs/**"); |
| } |
| |
| public RequestMatcher auditLogsRequestMatcher() { |
| return new AntPathRequestMatcher("/api/v1/audit/logs/**"); |
| } |
| |
| public RequestMatcher metadataRequestMatcher() { |
| return new AntPathRequestMatcher("/api/v1/metadata/**"); |
| } |
| |
| public RequestMatcher logsearchConfigRequestMatcher() { |
| return new AntPathRequestMatcher("/api/v1/shipper/**"); |
| } |
| |
| public RequestMatcher logLevelFilterRequestMatcher() { |
| return new AntPathRequestMatcher("/api/v1/shipper/filters/**"); |
| } |
| |
| public RequestMatcher shipperConfigInputRequestMatcher() { |
| return new AntPathRequestMatcher("/api/v1/shipper/input/**"); |
| } |
| |
| private String[] getCookies() { |
| List<String> cookies = new ArrayList<>(); |
| cookies.add(LOGSEARCH_SESSION_ID); |
| if (authPropsConfig.isAuthJwtEnabled()) { |
| cookies.add(authPropsConfig.getCookieName()); |
| } |
| return cookies.toArray(new String[0]); |
| } |
| |
| class LogSearchCompositeHeaderWriter implements HeaderWriter { |
| |
| private final boolean sslEnabled; |
| private final HeaderWriter[] additionalHeaderWriters; |
| private final HstsHeaderWriter hstsHeaderWriter; |
| |
| LogSearchCompositeHeaderWriter(boolean sslEnabled, HeaderWriter... additionalHeaderWriters) { |
| this.sslEnabled = sslEnabled; |
| this.additionalHeaderWriters = additionalHeaderWriters; |
| this.hstsHeaderWriter = new HstsHeaderWriter(); |
| } |
| |
| @Override |
| public void writeHeaders(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { |
| for (HeaderWriter headerWriter : additionalHeaderWriters) { |
| headerWriter.writeHeaders(httpServletRequest, httpServletResponse); |
| } |
| if (sslEnabled) { |
| hstsHeaderWriter.writeHeaders(httpServletRequest, httpServletResponse); |
| } |
| } |
| } |
| |
| } |