安全#

本文档中的信息主要关注基于云的部署。对于本地部署,还需要根据您的具体安装方式进行额外的安全工作。请注意,您特定安装的安全需求可能比我们在此处提供的内容更严格或更宽松。

Brad Geesamen 在 Kubecon NA 2017 上做了一场精彩的演讲,题为《通过示例攻防和加固 Kubernetes 集群》,您可以在这里观看演讲。强烈建议您观看,以了解使用 Kubernetes 运行 JupyterHub 时面临的安全问题。

报告安全问题#

如果您在 JupyterHub 中发现安全漏洞,无论是代码未能正确实现此处描述的模型,还是模型本身的失败,请报告至 security@ipython.org

如果您希望加密您的安全报告,可以使用此 PGP 公钥

HTTPS#

本节介绍如何在您的 JupyterHub 上启用 HTTPS。最简单的方法是使用 Let’s Encrypt,不过我们也会介绍如何设置您自己的 HTTPS 凭据。有关 HTTPS 安全的更多信息,请参阅这篇博客文章的证书部分。

设置您的域名#

  1. 从注册商处购买一个域名。选择您喜欢的任何一家即可。

  2. 为您要使用的域名创建一个 A 记录,指向 `proxy-public` 服务的 EXTERNAL-IP。具体操作方式取决于您使用的 DNS 提供商。

  3. 等待更改生效。生效过程可能需要几分钟到几个小时。请等到您可以在浏览器中输入您购买的域名并看到 JupyterHub 登录页面为止。

    耐心等待非常重要——过早进行下一步可能会导致问题!

设置自动 HTTPS#

JupyterHub 使用 Let’s Encrypt 自动为您的部署创建 HTTPS 证书。这将使您的 HTTPS 证书每隔几个月自动续期。要启用此功能,请对您的 config.yaml 文件进行以下更改:

  1. 指定自动配置 HTTPS 证书所需的两条信息——您的域名和一个联系电子邮件地址。

    proxy:
      https:
        enabled: true
        hosts:
          - <your-domain-name>
        letsencrypt:
          contactEmail: <your-email-address>
    
  2. 通过运行 helm upgrade ... 应用配置更改。

  3. 等待大约一分钟,现在您的 hub 应该已启用 HTTPS!


注意

如果代理服务的类型是 LoadBalancer(这是默认设置),那么可以请求一个特定的静态 IP 地址(如果可用),而不是动态获取的 IP。虽然对于 HTTPS 来说不是必需的,但为引用固定 IP 的域名使用静态 IP 地址是推荐的做法。这能确保多次部署使用相同的 IP 地址。可以通过以下方式提供 IP:

proxy:
  service:
    loadBalancerIP: xxx.xxx.xxx.xxx

有关此内容的更多信息可以在配置参考页面找到。


设置手动 HTTPS#

如果您有自己的 HTTPS 证书并希望使用它们,而不是自动配置的 Let’s Encrypt 证书,这也是可以的。请注意,这被视为高级选项,因此我们建议除非有充分理由,否则不要这样做。

有两种方法可以指定您的手动证书,直接在 `config.yaml` 中指定,或通过创建一个 Kubernetes secret

在 config.yaml 中指定证书#

  1. 将您的域名和 HTTPS 证书信息添加到您的 config.yaml 中:

    proxy:
      https:
        enabled: true
        type: manual
        manual:
          key: |
            -----BEGIN RSA PRIVATE KEY-----
            ...
            -----END RSA PRIVATE KEY-----
          cert: |
            -----BEGIN CERTIFICATE-----
            ...
            -----END CERTIFICATE-----
    
  2. 通过运行 helm upgrade ... 应用配置更改。

  3. 等待大约一分钟,现在您的 hub 应该已启用 HTTPS!

通过 Secret 资源指定证书#

  1. 创建一个类型为 kubernetes.io/tlssecret 资源,其中包含您的证书。

    kubectl create secret tls example-tls --key="tls.key" --cert="tls.crt"

  2. 将您的域名和 secret 的名称添加到您的 `config.yaml` 中。

    proxy:
      https:
        enabled: true
        hosts:
          - <your-domain-name>
        type: secret
        secret:
          name: example-tls
    
  3. 通过运行 helm upgrade ... 应用配置更改。

  4. 等待大约一分钟,现在您的 hub 应该已启用 HTTPS!

将 SSL 卸载到负载均衡器#

在某些具有可信网络的环境中,您可能希望在负载均衡器上终止 SSL。如果启用了 https,并且 proxy.https.type 设置为 offload,则 HTTP 和 HTTPS 前端将目标指向 JupyterHub 的 HTTP 端口。

负载均衡器上的 HTTPS 监听器需要根据提供商进行配置。如果您正在使用 AWS 及其证书管理器提供的证书,您的 `config.yml` 可能看起来像这样:

proxy:
  https:
    enabled: true
    type: offload
  service:
    annotations:
      # Certificate ARN
      service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:us-east-1:1234567891011:certificate/uuid"
      # The protocol to use on the backend, we use TCP since we're using websockets
      service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp"
      # Which ports should use SSL
      service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
      service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "3600"

注解选项将因提供商而异。

确认您的域名正在运行 HTTPS#

有很多方法可以确认一个域名正在运行受信任的 HTTPS 证书。一种选择是使用 Qualys SSL Labs 安全报告生成器。使用以下 URL 结构来测试您的域名:

https://ssllabs.com/ssltest/analyze.html?d=<YOUR-DOMAIN>

最小化的 hub 镜像#

默认的 hub 镜像包含一些有用的调试工具。您可以使用镜像的 slim 版本,以最大限度地减少您暴露于这些可选工具中漏洞的风险。

hub:
  image:
    # The slim variant excludes a few non-essential packages that are useful
    # when debugging something from the hub pod. To use it, apply this
    # configuration.
    #
    name: quay.io/jupyterhub/k8s-hub-slim

注意

我们使用 Linux Debian 作为基础镜像。有些容器扫描器会检测出 Debian 中已知的、但已被 Debian 安全团队驳回的漏洞。有关此问题的详细信息,请参阅此评论

安全访问 Helm#

Helm 3 支持现代 Kubernetes 的安全、身份和授权功能。Helm 的权限是使用您的 kubeconfig 文件进行评估的。集群管理员可以根据需要,以任何粒度限制用户权限。

Kubernetes 文档中阅读更多关于使用 kubeconfig 文件组织集群访问的信息。

删除 Kubernetes Dashboard#

Kubernetes Dashboard 在许多安装中默认被创建。虽然 Dashboard 包含有用的信息,但它也带来了安全风险。我们**建议**删除它,并且在 Dashboard 能够被妥善保护之前暂时不要使用。

您可以通过从集群中删除 Kubernetes Dashboard 部署来缓解此问题。这很可能可以通过以下命令执行:

kubectl --namespace=kube-system delete deployment kubernetes-dashboard

在较旧的集群中,您可能需要执行:

kubectl --namespace=kube-system delete rc kubernetes-dashboard

使用基于角色的访问控制 (RBAC)#

Kubernetes 支持并通常要求使用基于角色的访问控制 (RBAC) 来保护哪些 pod / 用户可以在集群上执行何种操作。可以设置 RBAC 规则,根据用户的管理需求为他们提供最小必要访问权限。

**至关重要**的一点是,如果 RBAC 被禁用,所有 pod 都将获得对 Kubernetes 集群及其所有节点的 root 等效权限。这会为您的安全带来非常严重的安全漏洞。

从与 JupyterHub 和 BinderHub 一起使用的 Helm chart v0.5 开始,该 Helm chart 可以在启用 RBAC 的集群中原生工作。为了提供合理的安全默认设置,我们为使用的各种组件提供了适当的最小化 RBAC 规则。我们**强烈建议**使用这些最小化或更严格的 RBAC 规则。

如果出于任何原因您想禁用 RBAC 规则,可以在您的 config.yaml 中使用以下代码片段:

rbac:
  create: false

我们强烈**不鼓励禁用** RBAC 规则,并提醒您此操作将带来安全漏洞。然而,某些云提供商可能不支持 RBAC,在这种情况下,您可以使用此配置禁用它。

Kubernetes API 访问#

允许用户直接访问 Kubernetes API 可能很危险。它允许用户授予自己更多权限,未经许可访问其他用户的内容,运行(无利可图的)比特币挖矿操作以及各种其他不合法的活动。出于这个原因,默认情况下,我们不允许用户服务器访问访问 Kubernetes API 所需的服务帐户凭据

如果您想(谨慎地!)为您的用户提供访问 Kubernetes API 的权限,可以在您的 config.yaml 中进行如下设置:

singleuser:
  serviceAccountName: <service-account-name>

您可以为您的用户手动创建一个服务帐户,并在此处指定其名称(推荐),或者使用 default 让他们访问命名空间的默认服务帐户。理想情况下,您还应该(手动)为此服务帐户设置 RBAC 规则,以指定用户将拥有的权限。

这是一个敏感的安全问题(类似于在传统计算环境中编写 sudo 规则),所以请务必小心。

我们正在努力使这项工作变得更容易!

审查云元数据服务器访问#

大多数云提供商都有一个静态 IP,pod 可以通过它获取有关云的元数据。这些元数据可能包含非常敏感的信息,如果落入坏人之手,攻击者可以完全控制您的集群和云资源。因此,**至关重要**的是保护元数据服务,防止可能在不知情的情况下运行恶意代码的用户 pod 访问它。

这个演示,从 27 分钟开始,提供了关于这种攻击所带来危险的更多信息。

此 Helm chart 默认通过两种方式阻止对该元数据的访问,但您只需要其中一种。

使用由 NetworkPolicy 控制器强制执行的 NetworkPolicy 阻止云元数据 API#

如果您的 Kubernetes 集群中有 *NetworkPolicy 控制器*(如 Calico 或 Cilium),它将强制执行此 chart 创建的 NetworkPolicy 资源(singleuser.networkPolicy.*),该资源默认不允许(因此会阻止)用户访问暴露在特定 IP(169.254.169.254)上的云元数据 API。

注意

如果您有 NetworkPolicy 控制器,我们建议依赖它,并将 singleuser.cloudMetadata.blockWithIptables 设置为 false

使用运行 iptables 的特权 initContainer 阻止云元数据 API#

如果您不能依赖 NetworkPolicy 方法来阻止对云元数据 API 的访问,我们建议依赖此选项。当 singleuser.cloudMetadata.blockWithIptables 为 true(默认值)时,一个 initContainer 会被添加到用户 pod 中。它将以提升的权限运行,并使用 iptables 命令行工具来阻止所有对云元数据服务器的网络访问。

# default configuration
singleuser:
  cloudMetadata:
    blockWithIptables: true
    ip: 169.254.169.254

在 3.0.0 版本中更改:此配置不允许与 singleuser.networkPolicy.egressAllowRules.cloudMetadataServer 同时配置为 true,以避免配置不明确。

Kubernetes 网络策略#

警告

您的 Kubernetes 集群可能会静默忽略此 Helm chart 可以创建的 NetworkPolicy 资源中描述的网络规则。NetworkPolicy 规则由一个可选的 NetworkPolicy 控制器强制执行,该控制器通常在设置 Kubernetes 集群时不会被配置。

默认情况下,此 Helm chart 会创建四个不同的 NetworkPolicy 资源,描述其所针对的 pod 允许哪些传入/ingress 和传出/egress 连接。

一个需要理解的关键点是,如果一个 pod 的 ingress 或 egress 连接分别没有被 NetworkPolicy 针对,它们将完全不受其约束。但如果被针对,那么只有明确允许的连接才会被接受。换句话说,定义一个针对 pod 的 NetworkPolicy 的行为本身就是约束,但 NetworkPolicy 中的所有规则都是允许规则。

Chart 的四个网络策略简介#

这四个网络策略为 Helm chart 创建的四种 pod 声明了规则。下面是一些表格,在一定程度上描述了这四个网络策略的作用。

网络策略

关联的 Helm chart 配置

受影响的 pod

pod 中的主要软件

hub

hub.networkPolicy

hub

jupyterhub, kubespawner, jupyterhub-idle-culler, Authenticator

proxy

proxy.chp.networkPolicy

proxy

configurable-http-proxy

autohttps

proxy.traefik.networkPolicy

autohttps

traefik, lego

singleuser

singleuser.networkPolicy

jupyter-<username>

jupyter_server

网络策略

为核心功能始终允许的出站连接 (egress)

hub

proxy pod 的 REST API 端口 (8001),用户 pod 的唯一端口 (8888)

proxy

hub pod 的唯一端口 (8081),用户 pod 的唯一端口 (8888)

autohttps

proxy pod 的 http 代理端口 (8000)

singleuser

hub pod 的唯一端口 (8081),proxy pod 的代理端口 (8000),autohttps pod 的 http (8080) 和 https (8443)

网络策略

为核心功能始终允许的入站连接 (ingress),允许来自带有特定标签的 pod 的特定端口的 ingress

hub

来自标记为 hub.jupyter.org/network-access-hub=true 的 pod

proxy

来自同一命名空间中标记为 hub.jupyter.org/network-access-proxy-http=true (http 代理端口) 或 hub.jupyter.org/network-access-proxy-api=true (REST API 端口) 的 pod

autohttps

来自标记为 hub.jupyter.org/network-access-proxy-http=true (http(s) 代理端口) 的 pod

singleuser

来自标记为 hub.jupyter.org/network-access-singleuser=true (notebook 端口) 的 pod

以上未总结所有功能

要完整记录这些网络策略的行为一直很棘手。有关深入的详细信息,请暂时参考检查 Helm chart 的模板以及根据您的配置渲染出的结果。

以下是 Helm chart 模板的链接。

以下是您可以用来渲染特定模板的命令。

# These four commands renders the four NetworkPolicy resource templates of the
# latest release of the JupyterHub Helm chart, with default values.
#
# You can pass `--values <your config file>` or `--version <version here>` to
# these commands to inspect the rendered NetworkPolicy resources given your
# specific version and configuration.
#
helm template --repo https://jupyterhub.github.io/helm-chart jupyterhub --show-only templates/hub/netpol.yaml
helm template --repo https://jupyterhub.github.io/helm-chart jupyterhub --show-only templates/proxy/netpol.yaml
helm template --repo https://jupyterhub.github.io/helm-chart jupyterhub --show-only templates/proxy/autohttps/netpol.yaml
helm template --repo https://jupyterhub.github.io/helm-chart jupyterhub --show-only templates/singleuser/netpol.yaml

启用和禁用网络策略#

NetworkPolicy 资源默认创建,并且在创建时会将入站和出站网络连接限制为 NetworkPolicy 资源中明确允许的连接。要选择不创建 NetworkPolicy 资源,请使用如下配置。

# Example configuration on how to disable the creation of all the Helm chart's
# NetworkPolicy resources.
hub:
  networkPolicy:
    enabled: false
proxy:
  chp:
    networkPolicy:
      enabled: false
  traefik:
    networkPolicy:
      enabled: false
singleuser:
  networkPolicy:
    enabled: false

允许额外的入站网络连接 (ingress)#

除了确保核心功能的规则外,您还可以使用 <hub|proxy.chp|proxy.traefik|singleuser>.networkPolicy.ingress 配置添加任意允许规则。您也可以为您想要允许建立连接到 Helm chart 各种 pod 的 pod 添加标签。

例如,要从同一命名空间中的另一个 pod 访问 hub pod,只需将标签 hub.jupyter.org/network-access-hub: "true" 添加到应该能够与 hub pod 建立连接的 pod 即可。

可用的访问标签有:

  • hub.jupyter.org/network-access-hub: "true", 访问 hub api

  • hub.jupyter.org/network-access-proxy-http: "true", 访问代理的公共 http 端点

  • hub.jupyter.org/network-access-proxy-api: "true", 访问代理 api

  • hub.jupyter.org/network-access-singleuser: "true", 直接访问 singleuser 服务器

如果您希望从另一个命名空间使用这些标签访问 pod,请阅读关于 <hub|proxy.chp|proxy.traefik|singleuser>.networkPolicy.interNamespaceAccessLabels 的内容。

最后,选项 <hub|proxy.chp|proxy.traefik|singleuser>.networkPolicy.allowedIngressPorts 使您能够允许特定 pod 上的传入连接。

允许额外的出站网络连接 (egress)#

除了确保核心功能的规则外,您还可以使用 <hub|proxy.chp|proxy.traefik|singleuser>.networkPolicy.egress 配置添加任意允许规则。您也可以打开或关闭一些预定义的允许规则。它们在配置参考的 <hub|proxy.chp|proxy.traefik|singleuser>.networkPolicy.egressAllowRules 下有文档说明。

默认情况下,所有 egress 允许规则都为 hub, proxy.chp, 和 proxy.traefik 启用,但 singleuser.networkPolicy.egressAllowRules.cloudMetadataServersingleuser.networkPolicy.egressAllowRules.privateIPs 默认为 false。在实践中,这可能意味着没有规则允许用户 pod 与某些使用私有 IPv4 地址的 k8s 本地服务通信。

在 2.0.0 版本中更改:在 JupyterHub Helm chart 2.0.0 之前,默认配置是允许 singleuser pod 与任何对象建立出站连接。2.0.0 之后,必须显式设置 singleuser.networkPolicy.egressAllowRules.privateIPs=true 才能实现此目的。

限制负载均衡器访问#

默认情况下,任何 IP 地址都可以通过负载均衡器服务访问您的 JupyterHub 部署。如果您想限制允许访问负载均衡器的 IP 地址,可以在您的 config.yaml 文件中指定一个 IP CIDR 地址列表,如下所示:

proxy:
  service:
    loadBalancerSourceRanges:
      - 111.111.111.111/32
      - 222.222.222.222/32

这将限制访问权限,仅允许两个 IP 地址:111.111.111.111222.222.222.222

在子域名上托管用户服务器#

您可以通过为每个用户提供他们自己的子域名 <user>.jupyter.example.org 来减少跨源攻击的风险。这需要设置 subdomain_host,创建一个通配符 DNS 记录 *.jupyter.example.org,并创建一个通配符 SSL 证书。

hub:
  config:
    JupyterHub:
      subdomain_host: jupyter.example.org

如果您正在使用 Kubernetes ingress,这必须包括主机 jupyter.example.org*.jupyter.example.org。例如:

ingress:
  enabled: true
  hosts:
    - jupyter.example.org
    - "*.jupyter.example.org"
  tls:
    - hosts:
        - jupyter.example.org
        - "*.jupyter.example.org"
      secretName: example-tls

其中 example-tls 是包含通配符证书和密钥的 Kubernetes secret 的名称。

该 chart 不支持自动创建通配符 HTTPS 证书。您必须从外部来源获取证书,例如通过使用 ACME 客户端,如使用 DNS-01 质询的 cert-manager,并确保证书和密钥存储在 secret 中。

有关更多信息,请参阅 JupyterHub 文档中的启用用户子域名