技术

mysql 事务 mysql 索引 坏味道 学习分布式 学习网络 学习Linux go 内存管理 golang 系统调用与阻塞处理 图解Goroutine 调度 重新认识cpu mosn有的没的 负载均衡泛谈 单元测试的新解读 《Redis核心技术与实现》笔记 《Prometheus监控实战》笔记 Prometheus 告警学习 calico源码分析 对容器云平台的理解 Prometheus 源码分析 并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go channel codereview gc分析 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes垂直扩缩容 神经网络模型优化 直觉上理解机器学习 knative入门 如何学习机器学习 神经网络系列笔记 TIDB源码分析 什么是云原生 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全机制 jvm crash分析 Prometheus 学习 容器日志采集 Kubernetes 控制器模型 Kubernetes监控 容器狂占资源怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects 源码分析体会 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 jib源码分析之细节 线程排队 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI——容器网络是如何打通的 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 精简代码的利器——lombok 学习 《深入剖析kubernetes》笔记 编程语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 calico学习 AQS——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 学习并发 docker运行java项目的常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型 java exception Linux IO学习 netty内存管理 测试环境docker化实践 netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker中涉及到的一些linux知识 Linux网络源代码学习——整体介绍 zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 Redis概述 机器学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM内存与执行 git spring rmi和thrift maven/ant/gradle使用 再看tcp 缓存系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 Go学习 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

架构

openkruise学习 可观察性 基于Kubernetes选主及应用 《许式伟的架构课》笔记 Kubernetes webhook 发布平台系统设计 k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 controller 组件介绍 openkruise cloneset学习 kubebuilder 及controller-runtime学习 pv与pvc实现 csi学习 client-go学习 kubelet 组件分析 调度实践 Pod是如何被创建出来的? 《软件设计之美》笔记 mecha 架构学习 Kubernetes events学习及应用 CRI 《推荐系统36式》笔记 资源调度泛谈 系统设计原则 grpc学习 元编程 以应用为中心 istio学习 下一代微服务Service Mesh 《实现领域驱动设计》笔记 serverless 泛谈 《架构整洁之道》笔记 处理复杂性 那些年追过的并发 服务器端编程 网络通信协议 《聊聊架构》 书评的笔记 如何学习架构 《反应式设计模式》笔记 项目的演化特点 反应式架构摸索 函数式编程的设计模式 服务化 ddd反模式——CRUD的败笔 研发效能平台 重新看面向对象设计 业务系统设计的一些体会 函数式编程 《左耳听风》笔记 业务程序猿眼中的微服务管理 DDD实践——CQRS 项目隔离——案例研究 《编程的本质》笔记 系统故障排查汇总及教训 平台支持类系统的几个点 代码腾挪的艺术 abtest 系统设计汇总 《从0开始学架构》笔记 初级权限系统设计 领域驱动理念入门 现有上传协议分析 移动网络下的文件上传要注意的几个问题 推送系统的几个基本问题 用户登陆 做配置中心要想好的几个基本问题 不同层面的异步 分层那些事儿 性能问题分析 当我在说模板引擎的时候,我在说什么 用户认证问题 资源的分配与回收——池 消息/任务队列

标签


docker和k8s安全机制

2019年03月29日

简介

火得一塌糊涂的kubernetes有哪些值得初学者学习的? Kubernetes 是透明的,它没有隐藏的内部 API。换句话说 Kubernetes 系统内部用来交互的 API 和我们用来与 Kubernetes 交互的 API 相同。这样做的好处是,当 Kubernetes 默认的组件无法满足我们的需求时,我们可以利用已有的 API 实现我们自定义的特性。

docker 安全

最典型的例子,将/etc 等核心文件 挂载到容器中,在容器中进行改写等。

绝不避谈 Docker 安全

Linux capabilities

  1. 在 Linux capabilities 出现前,进程的权限可以简单分为两类:特权用户的进程(id=0);非特权用户的进程(id>0)
  2. 从 kernel 2.2 开始,Linux 把特权用户所有的这些“特权”做了更详细的划分,这样被划分出来的每个单元就被称为 capability。比如说,运行 iptables 命令,对应的进程需要有 CAP_NET_ADMIN 这个 capability。如果要 mount 一个文件系统,那么对应的进程需要有 CAP_SYS_ADMIN 这个 capability。
  3. 在普通 Linux 节点上,非 root 用户启动的进程缺省没有任何 Linux capabilities,而 root 用户启动的进程缺省包含了所有的 Linux capabilities。对于 root 用户启动的进程,如果把 CAP_NET_ADMIN 这个 capability 移除(capsh --keep=1 --user=root --drop=cap_net_admin),它就不可以运行 iptables。
  4. 新运行的进程里的相关 capabilities 参数的值,是由它的父进程以及程序文件中的 capabilities 参数值计算得来的(文件中可以设置 capabilities 参数值(以ping 为例,getcap $(which ping)/setcap -r $(which ping)),并且这个值会影响到最后运行它的进程)。如果把 iptables 的应用程序加上 CAP_NET_ADMIN 的 capability,那么即使是非 root 用户也有执行 iptables 的权限了。
  5. 因为安全方面的考虑,容器缺省启动的时候,哪怕是容器中 root 用户的进程,系统也只允许了 15 个 capabilities。Privileged 的容器也就是允许容器中的进程可以执行所有的特权操作。如果我们发现容器中进程的权限不够,就需要分析它需要的最小 capabilities 集合,而不是直接赋予容器”privileged”。

docker 多用户

作为容器中的 root,它还是可以有一些 Linux capabilities,那么在容器中还是可以执行一些特权的操作。怎么办呢?

可以给容器指定一个普通用户。docker run -ti --name root_example -u 6667:6667 -v /etc:/mnt centos bash 或者在创建镜像时,加入USER $username。这样做的缺点是:用户 uid 是整个节点中共享的,比如说,多个客户在建立自己的容器镜像的时候都选择了同一个 uid 6667。那么当多个客户的容器在同一个节点上运行的时候,其实就都使用了宿主机上 uid 6667。在一台 Linux 系统上,每个用户下的资源是有限制的,比如打开文件数目(open files)、最大进程数目(max user processes)等等。一旦有很多个容器共享一个 uid,这些容器就很可能很快消耗掉这个 uid 下的资源,这样很容易导致这些容器都不能再正常工作。

也可以使用User Namespace(一些组件默认不启用),User Namespace 隔离了一台 Linux 节点上的 User ID(uid)和 Group ID(gid),它给 Namespace 中的 uid/gid 的值与宿主机上的 uid/gid 值建立了一个映射关系。经过 User Namespace 的隔离,我们在 Namespace 中看到的进程的 uid/gid,就和宿主机 Namespace 中看到的 uid 和 gid 不一样了。比如podman run -ti -v /etc:/mnt --uidmap 0:2000:1000 centos bash,第一个 0 是指在新的 Namespace 里 uid 从 0 开始,中间的那个 2000 指的是 Host Namespace 里被映射的 uid 从 2000 开始,最后一个 1000 是指总共需要连续映射 1000 个 uid。这个容器里的 uid 0 是被映射到宿主机上的 uid 2000 的,把容器中 root 用户(uid 0)映射成宿主机上的普通用户。

rootless container 中的”rootless”不仅仅指容器中以非 root 用户来运行进程,还指以非 root 用户来创建容器,管理容器。也就是说,启动容器的时候,Docker 或者 podman 是以非 root 用户来执行的。

访问权限控制

一文读懂 TKE 及 Kubernetes 访问权限控制

认证

认证的过程即是证明user身份的过程。Kubernetes中有两类用户:

  1. ServiceAccount账户是由Kubernetes提供API(资源)进行创建和管理的,ServiceAccount可以认为是特殊的Secret资源(创建一个ServiceAccount 默认会创建一个Secret),可作为用户集群内资源访问APIServer的认证所用。可以通过mount的方式挂载到Pod内进行使用。
  2. 真实的用户通常是从外部发起请求访问APIServer,由管理员管理认证凭证,而Kubernetes本身不管理任何的用户和凭证信息的,即所有的用户都是逻辑上的用户,无法通过API调用Kubernetes API进行创建真实用户。

Managing Service Accounts

  1. User accounts are for humans. Service accounts are for processes, which run in pods.
  2. User accounts are intended to be global. Names must be unique across all namespaces of a cluster, future user resource will not be namespaced. Service accounts are namespaced.

任何的认证方式都是一下Interface的实现方式都是接收http Request请求,然后会返回一个user.Info的结构体,一个bool,以及一个error。user.Info中包含了用户的信息,包括UserName、UUID、Group、Extra。 bool返回了用户是否通过认证,false的话即返回无法通过认证,即返回401错误。error则返回了当Request无法被检查的错误,如果遇到错误则会继续进行下一种注册的方式进行认证。 如果认证通过,则会把user.Info写入到到请求的context中,后续请求过程可以随时获取用户信息,比如授权时进行鉴权。

// Request attempts to extract authentication information from a request and returns
// information about the current user and true if successful, false if not successful,
// or an error if the request could not be checked.
type Request interface {
   AuthenticateRequest(req *http.Request) (user.Info, bool, error)
}

APIServer启动时,可以指定一种或多种Authentication方法,如果指定了多种方法,那么APIServer将会逐个使用这些方法对客户端请求进行验证,只要通过其中一种方法的验证,APIServer就会认为Authentication成功;

  APIServer启动参数 请求
Basic Authentication csv文件
--basic-auth-file=SOMEFILE
HTTP Header中Authentication为Basic,并跟上Base64Encode(user:passward)值
x509 客户端证书 --client-ca-file=SOMEFILE  
Bearer Token(有多种方式) csv文件--token-auth-file=SOMEFILE HTTP Header中Authentication为Bearer,并跟上Base64Encode(user:passward)值
Webhook Token Server --authentication-token-webhook-config-file
--authentication-token-webhook-cache-ttl
 

若APiServer开启Webhook Token Server进行认证校验,则在接受到用户的Request之后,会包装Bearer Token成一个TokenReview发送给WebHookServer,Server端接收到之后会进行校验,并返回TokenReview接口,在status字段中进行反馈是否通过校验通过和user.Info信息。

授权

授权就是判断user是否拥有操作资源的相应权限。

这个阶段面对的输入是http request context中的各种属性,包括:user、group、request path(比如:/api/v1、/healthz、/version等)、request verb(比如:get、list、create等)。APIServer会将这些属性值与事先配置好的访问策略(access policy)相比较。APIServer支持多种authorization mode,包括AlwaysAllow、AlwaysDeny、ABAC、RBAC和Webhook。APIServer启动时,可以指定一种或多种authorization mode,和认证一样,只要有一种鉴权模块通过,即可返回资源。

  1. RBAC, Kubernetes提供ClusterRole、Role资源,分别对应集群维度、Namespace维度角色权限管控,用户可以自定义相应的ClusterRole、Role资源,绑定到已经认证的User之上。下例中 通过认证模块到达授权模块的requestInfo中userInfo信息是alex的请求,在授权模块中走到RBAC授权模块时,则会进行查询集群的ClusterRole/ClusterRoleBinding信息。进行判断是否拥有context相应操作的权限。

     apiVersion: rbac.authorization.k8s.io/v1
     kind: ClusterRole
     metadata:
     name: tke:pod-reader
     rules:
     - apiGroups: [""] # "" 指定核心 API 组
     resources: ["pods"]
     verbs: ["get", "watch", "list"]
        
     ---
     apiVersion: rbac.authorization.k8s.io/v1
     # 此角色绑定使得用户 "alex" 能够读取 "default" 命名空间中的 Pods
     kind: ClusterRoleBinding
     metadata:
     name: alex-ClusterRole
     subjects:
     - kind: User
     name: alex
     apiGroup: rbac.authorization.k8s.io
     roleRef:
     kind: ClusterRole
     name: tke:pod-reader # 这里的名称必须与你想要绑定的 Role 或 ClusterRole 名称一致
     apiGroup: rbac.authorization.k8s.io
    
  2. WebHook, Webhook模式是一种基于HTTP回调的方式,通过配置好授权webhook server地址。当APIServer接收到request的时候,会进行包装SubjectAccessReview请求Webhook Server,Webhook Server会进行判断是否可以访问,然后返回allow信息。

准入控制

从技术的角度看,Admission control就像a chain of interceptors(拦截器链模式),它拦截那些已经顺利通过authentication和authorization的http请求。http请求沿着APIServer启动时配置的admission control chain顺序逐一被拦截和处理,如果某个interceptor拒绝了该http请求,那么request将会被直接reject掉,而不是像authentication或authorization那样有继续尝试其他interceptor的机会。

K8s支持30多种admission control 插件,其中有两个具有强大的灵活性,即ValidatingAdmissionWebhooks和MutatingAdmissionWebhooks,这两种控制变换和准入以Webhook的方式提供给用户使用,大大提高了灵活性,用户可以在集群创建自定义的AdmissionWebhookServer进行调整准入策略。

安全相关的 kubernetes objects

Secrets——非明文的configmap

  1. 为什么弄一个Secrets?
  2. 其它组件(主要是Pod)如何获取Secrets数据?

Secrets

为什么要弄一个Secrets? 回答为什么的一般方法是:对比前后差异

Kubernetes secret objects let you store and manage sensitive information, such as passwords, OAuth tokens, and ssh keys. Putting this information in a secret is safer and more flexible than putting it verbatim(原样的) in a Pod Lifecycle definition or in a container image . 管理敏感信息,如果没有Secrets,这些信息可能被保存在 pod 定义或者 docker 镜像中,有信息泄露的风险(PS:笔者曾把密码写在代码测试类里提交到公司仓库, 结果被几个看源码的小哥发现并使用了)

K8s Secrets 中保存的数据都必须经过 base64加密

apiVersion: v1
kind: Secret
metadata:
    name: db-secret
    type: Opaque
    data:
        # base64
        carrot-db-username: Y2Fycm90
        carrot-db-password: Y2Fycm90

Secrets can be mounted as data volumes or be exposed as environment variables to be used by a container in a pod

  1. volume 方式, Each key in the secret data map becomes the filename under mountPath. 每一个secret key 会变成 container mountPath 下的一个文件。When a secret being already consumed in a volume is updated, projected keys are eventually updated as well. Kubelet is checking whether the mounted secret is fresh on every periodic sync. 当你改了secret 数据,则容器内的文件内容也能自动同步
  2. Environment Variables

Service Accounts——让pod 可以访问apiserver

配置 Pod 的 Service Account service account 只是提供了一个类似用户名的标识,真正ServiceAccount 有哪些权限要 通过 ClusterRoleBinding 绑定 ClusterRole。

A service account provides an identity for processes that run in a Pod. When you (a human) access the cluster (for example, using kubectl), you are authenticated by the apiserver as a particular User Account (currently this is usually admin, unless your cluster administrator has customized your cluster). Processes in containers inside pods can also contact the apiserver. When they do, they are authenticated as a particular Service Account (for example, default).

apiVersion: v1
kind: ServiceAccount
metadata:
namespace: mynamespace
name: example-sa

Kubernetes 会为一个 ServiceAccount自动创建并分配一个 Secret 对象,secret {data.token} 就是用户 token 的 base64 编码,可以用来配置kubeconfig

$ kubectl get sa -n mynamespace -o yaml
- apiVersion: v1
kind: ServiceAccount
metadata:
    creationTimestamp: 2018-09-08T12:59:17Z
    name: example-sa
    namespace: mynamespace
    resourceVersion: "409327"
    ...
secrets:
- name: example-sa-token-vmfg6
$ k describe secret  example-sa-token-vmfg6
Name:         example-sa-token-vmfg6
Namespace:    mynamespace
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: example-sa
              kubernetes.io/service-account.uid: 0fc75075-41b9-48e5-af75-c1b31d64955b

Type:  kubernetes.io/service-account-token
Data
====
ca.crt:     1346 bytes
namespace:  11 bytes
token:      xx      # base64 编码,反解析后是一个json 

用户的 Pod可以声明使用这个 ServiceAccount

apiVersion: v1
kind: Pod
metadata:
namespace: mynamespace
name: sa-token-test
spec:
containers:
- name: nginx
    image: nginx:1.7.9
serviceAccountName: example-sa

等这个 Pod 运行起来之后,我们就可以看到,该 ServiceAccount 的 token,也就是一个 Secret 对象,被 Kubernetes 自动挂载到了容器的 /var/run/secrets/kubernetes.io/serviceaccount 目录下

$ kubectl describe pod sa-token-test -n mynamespace
Name:               sa-token-test
Namespace:          mynamespace
...
Containers:
nginx:
    ...
    Mounts:
    /var/run/secrets/kubernetes.io/serviceaccount from example-sa-token-vmfg6 (ro)

小结一下

  1. 有哪些资源
  2. Role 表述对这些资源的操作能力
  3. User/ServiceAccount 和 Role 绑定在一起,进而拥有Role 所具有的能力
  4. Pod 和 ServiceAccount 绑定在一起, Pod内进程可以使用 k8s push到本地的ServiceAccount 数据访问 api resource

开阔下视野Kata Containers 创始人带你入门安全容器技术