blob: 98b0ab77acdfbe4cf7e02cac9aa4c6295126b129 [file] [log] [blame]
/*
* 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.io.File;
import java.nio.charset.Charset;
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.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.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 {
private static final Logger logger = LogManager.getLogger(SecurityConfig.class);
@Inject
private AuthPropsConfig authPropsConfig;
@Inject
private LogSearchHttpHeaderConfig logSearchHttpHeaderConfig;
@Inject
private LogSearchHttpConfig logSearchHttpConfig;
@Inject
private LogSearchSslConfig logSearchSslConfig;
@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 LogsearchAuthenticationProvider logsearchAuthenticationProvider;
@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());
}
char[] ldapPassword = getLdapManagerPassword();
if (ldapPassword != null) {
ldapContextSource.setPassword(new String(ldapPassword));
}
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 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 char[] getLdapManagerPassword() {
char[] ldapPassword = null;
try {
String credentialProviderPath = logSearchSslConfig.getCredentialStoreProviderPath();
String ldapPasswordEnv = "LOGSEARCH_LDAP_MANAGER_PASSWORD";
if (StringUtils.isNotBlank(credentialProviderPath)) {
org.apache.hadoop.conf.Configuration config = new org.apache.hadoop.conf.Configuration();
config.set(LogSearchSslConfig.CREDENTIAL_STORE_PROVIDER_PATH, credentialProviderPath);
ldapPassword = config.getPassword("logsearch.auth.ldap.manager.password");
} else if (StringUtils.isNotBlank(authPropsConfig.getLdapAuthConfig().getLdapManagerPasswordFile())){
ldapPassword = FileUtils.readFileToString(new File(
authPropsConfig.getLdapAuthConfig().getLdapManagerPasswordFile()), Charset.defaultCharset()).toCharArray();
} else if (StringUtils.isNotBlank(System.getenv(ldapPasswordEnv))) {
ldapPassword = System.getenv(ldapPasswordEnv).toCharArray();
} else if (StringUtils.isNotBlank(authPropsConfig.getLdapAuthConfig().getLdapManagerPassword())) {
ldapPassword = authPropsConfig.getLdapAuthConfig().getLdapManagerPassword().toCharArray();
}
} catch (Exception e) {
logger.warn("Error during ldap password initialization. LDAP authentication probably won't work if a manager password will be required.", e);
}
return ldapPassword;
}
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);
}
}
}
}