将适用于 Kubernetes 的 NGINX Plus 入口控制器与 Azure AD 中的 OpenID Connect 身份验证结合使用

table.nginx-blog, table.nginx-blog th, table.nginx-blog td {
边框:2px 纯黑;
边界崩溃:崩溃;
}
表.nginx-博客 {
宽度:100%;
}
table.nginx-blog th {
背景颜色:#d3d3d3;
对齐:左;
左内边距:5px;
右内边距:5px;
底部填充:2px;
顶部填充:2px;
行高:120%;
}
表.nginx-博客 td {
左内边距:5px;
右内边距:5px;
底部填充:2px;
顶部填充:5px;
行高:120%;
}
表.nginx-博客 td.center {
文本对齐:居中;
底部填充:2px;
顶部填充:5px;
行高:120%;
}

编辑器 – 由于 NGINX Plus R22 的 NGINX OpenID Connect 参考实现得到增强,本博客中描述的过程不适用于使用 OpenID Connect 与 NGINX Plus R22 及更高版本进行身份验证。对于替代方案有关此方法,请参阅 Tom 的 Ansible 角色,该角色有助于生成与 NGINX Ingress Controller 和 NGINX Plus R22 及更高版本一起使用的配置。

在 NGINX Ingress Controller 1.10.0 及更高版本中,受支持的替代方案是 OIDC 策略资源。有关更多信息,请参阅我们博客上的使用 OpenID Connect 和 NGINX Ingress Controller 实现简单且强大的单点登录。

<!–

NGINX 开源已经是 Kubernetes 的默认 Ingress 资源,但 NGINX Plus 提供了额外的企业级功能,包括 JWT 验证、会话持久性和大量指标。在本博客中,我们展示了如何使用 NGINX Plus 对 Kubernetes 环境中 Ingress 背后的应用程序和资源执行 OpenID Connect (OIDC) 身份验证,并在简化扩展部署的设置中进行。

下图描述了使用此设置的身份验证过程:

要创建设置,请执行以下部分中的步骤:

  • 获得信誉来自 OIDC 身份提供商 (Azure Active Directory) 的必需信息
  • 安装和配置 Kubernetes
  • 为 NGINX Plus Ingress 控制器创建 Docker 映像
  • 安装和自定义 NGINX Plus Ingress 控制器
  • 设置示例应用程序以使用 OpenID Connect

笔记:

  • 本博客仅用于演示和测试目的,说明如何使用 NGINX Plus 在 Kubernetes 中使用 OIDC 凭据进行身份验证。该设置不一定包含在您的 NGINX Plus 支持合同中,也不适用于未经修改以满足您组织的安全和治理要求的生产工作负载。
  • 几位 NGINX 同事合作撰写了此博客,我感谢他们的贡献。我特别要感谢首先提出这个用例的 NGINX 同事(他谦虚地希望保持匿名)!

从 OpenID Connect 身份提供商 (Azure Active Directory) 获取凭据

OpenID Connect (OIDC) 的目的是使用已建立的、众所周知的用户身份,而不增加身份提供商(ODC 术语中的 IdP)的攻击面。我们的应用程序信任 IdP,因此当它调用 IdP 对用户进行身份验证时,它会愿意使用身份验证证明来控制对资源的授权访问。

在此示例中,我们使用 Azure Active Directory (AD) 作为 IdP,但您可以选择目前运行的众多 OIDC IdP 中的任何一个。例如,我们之前的博客文章使用 OpenID Connect 和 NGINX Plus 对现有应用程序的用户进行身份验证使用了 Google。

要将 Azure AD 用作 IdP,请执行以下步骤,将示例值替换为适合您的应用程序的值:

  • 如果您尚未使用 Azure,请创建一个帐户。

  • 导航到 Azure 门户并单击左侧导航栏中的 Azure Active Directory。

    在本博客中,我们使用的是高级版 AD 中提供的功能,而不是标准免费版本中提供的功能。如果您还没有高级版本(新帐户的情况也是如此),您可以按照 AD 概述页面上的提示开始免费试用。

  • 点击左侧导航栏中的应用程序注册(我们已最小化屏幕截图中的全局导航栏)。

  • 在“应用程序注册”页面上,单击“新注册”。

  • 在打开的“注册应用程序”页面上,在“名称”和“重定向 URI”字段中输入值,单击“支持的帐户类型”部分中相应的单选按钮,然后单击“注册”按钮。我们使用以下值:

    • 名称 – 咖啡馆
    • 支持的帐户类型 – 仅限此组织目录中的帐户
    • 重定向 URI(可选)– 网页:https://cafe.nginx.net/_codexch
  • 记下打开的咖啡馆确认页面上的应用程序(客户端)ID 和目录(租户)ID 字段中的值。我们将它们添加到我们在设置示例应用程序以使用 OpenID Connect 中创建的 Cafe-ingress.yaml 文件中。

  • 在左侧导航栏的“管理”部分中,单击“证书和机密”(请参阅​​前面的屏幕截图)。在打开的页面上,单击“新建客户端密钥”按钮。

  • 在“添加客户端密钥”弹出窗口中,输入以下值并单击“添加”按钮:

    • 描述 – client_secret
    • 过期 – 永不过期
  • 复制出现的 client_secret 值,因为关闭窗口后该值将无法恢复。在我们的示例中,它是 kn_3VLh]1I3ods*[DDmMxNmg8xxx。

  • 对客户端密钥进行 URL 编码。有多种方法可以做到这一点,但对于非生产示例,我们可以使用 urlencoder.org 网站。将密码粘贴到上方的灰色框中,单击  > ENCODE <  按钮,编码值将出现在下方的灰色框中。复制编码值以在配置文件中使用。在我们的示例中,它是 kn_3VLh%5D1I3ods%2A%5BDDmMxNmg8xxx。

  • 安装和配置 Kubernetes

    安装和配置 Kubernetes 的方法有很多,但在本例中,我们将使用我最喜欢的安装程序之一 Kubespray。您可以从 GitHub 存储库安装 Kubespray。

    您可以在任何您希望的平台上创建 Kubernetes 集群。这里我们使用的是 MacBook。我们之前使用 VMware Fusion 在 MacBook 上创建了四个虚拟机 (VM)。我们还创建了一个自定义网络,支持使用网络地址转换(N在)。要在 Fusion 中启用 NAT,请导航到首选项 > 网络,创建新的自定义网络,然后通过展开高级部分并选中 NAT 选项来启用 NAT。

    VM 具有以下属性:

    姓名

    操作系统

    IP地址

    别名 IP 地址

    记忆

    磁盘大小

    节点1 CentOS 7.6 172.16.186.101 172.16.186.100 4 GB 20 GB 节点2 CentOS 7.6 172.16.186.102 – 2 GB 20 GB 节点3 CentOS 7.6 172.16.186.103 – 2 GB 20 GB 节点4 CentOS 7.6 172.16.186.104 – 2 GB 20 GB

    请注意,我们为每个节点设置了一个静态 IP 地址,并在 node1 上创建了一个别名 IP 地址。在此外,我们满足 Kubernetes 节点的以下要求:

    • 禁用交换
    • 允许 IP 地址转发
    • 将 ssh 密钥从运行 Kubespray(Macbook)的主机复制到四个虚拟机中的每一个,以启用无需密码的 ssh 连接
    • 修改四个虚拟机中每一个虚拟机上的 sudoers 文件,以允许在没有密码的情况下使用 sudo(使用 visudo 命令并进行以下更改):

      [config]## 允许群组wheel中的人运行所有命令
      # %车轮全部=(全部)全部

      ## 没有密码也一样
      %Wheel ALL=(ALL) NOPASSWD: ALL[/config]

    我们在虚拟机上禁用了firewalld,但对于生产,您可能希望保持其启用状态并定义防火墙接受流量的端口。我们的 SELinux 处于强制模式。

    在 MacBook 上,我们还满足了所有 Kubespray 先决条件,包括安装 Kubespray 支持的 Ansible 版本。

    Kubespray 附带了一些配置配置文件。我们将替换其中两个字段中几个字段的值:

    • group_vars/all/all.yml

      [config]#添加调用上游DNS的能力
      上游 DNS 服务器:
      – 8.8.8.8
      – 8.8.4.4[/config]

    • group_vars/k8s-cluster/k8s-cluster.yml

      [配置]kube_network_plugin:法兰绒
      # 确保以下子网未被活动网络使用
      kube_service_地址:10.233.0.0/18
      kube_pods_子网:10.233.64.0/18
      # 将集群名称更改为您打算使用的名称
      集群名称:k8s.nginx.net
      # 添加以便我们在本地获取 kubectl 和配置文件
      kubeconfig_localhost:true
      kubectl_localhost:true[/config]

    我们还创建一个新的hosts.yml 文件,其中包含以下内容:

    [配置]全部:
    主持人:
    节点1:
    ansible_主机:172.16.186.101
    ip: 172.16.186.101
    访问IP:172.16.186.101
    节点2:
    ansible_主机:172.16.186.102
    ip: 172.16.186.102
    访问IP:172.16.186.102
    节点3:
    安西布尔_主机:172.16.186.103
    ip: 172.16.186.103
    访问IP:172.16.186.103
    节点4:
    ansible_主机:172.16.186.104
    ip: 172.16.186.104
    访问IP:172.16.186.104
    孩子们:
    库贝大师:
    主持人:
    节点1:
    kube 节点:
    主持人:
    节点1:
    节点2:
    节点3:
    节点4:
    等:
    主持人:
    节点1:
    k8s 集群:
    孩子们:
    库贝大师:
    kube 节点:
    印花布-rr:
    主机:{}[/config]
    现在,我们运行以下命令来创建一个四节点 Kubernetes 集群,其中 node1 作为单个主节点。 (Kubernetes 建议在生产环境中使用三个主节点,但对于我们的示例来说,一个节点就足够了,并且消除了任何可能的同步问题。)

    [终端]$ ansible-playbook -i inventory/mycluster/hosts.yml -b cluster.yml[/terminal]

    为 NGINX Plus Ingress 控制器创建 Docker 映像

    NGINX 发布了一个用于开源的 Docker 镜像rce NGINX Ingress Controller,但我们使用的是 NGINX Plus,因此需要使用与 NGINX Plus 订阅关联的证书和密钥构建私有 Docker 映像。我们按照 NGINX Ingress Controller 的 GitHub 存储库中的说明进行操作,但替换该存储库中提供的 Dockerfile 的内容,如下所述。

    注意:请确保将镜像存储在私有 Docker Hub 存储库中,而不是标准的公共存储库中;否则您的 NGINX Plus 凭据会暴露并可能被滥用。免费的 Docker Hub 帐户使您有权使用一个私人存储库。

    将 kubernetes-ingress 存储库中提供的标准 Dockerfile 的内容替换为以下文本。一个重要的区别是,我们通过将 nginx-plus-module-njs 参数添加到第二个 apt-get install 命令中,将 NGINX JavaScript (njs) 模块包含在 Docker 映像中。

    [配置滚动=“true”]来自 debian:stretch-slim

    标签维护者=“NGINX Docker 维护者”

    环境NGINX_PLUS_版本 18-1~拉伸
    ARG IC_VERSION

    # 从客户门户(https://cs.nginx.com)下载证书和密钥
    # 并复制到构建上下文
    复制 nginx-repo.crt /etc/ssl/nginx/
    复制 nginx-repo.key /etc/ssl/nginx/

    # 确保证书和密钥具有正确的权限
    运行 chmod 644 /etc/ssl/nginx/*

    # 安装 NGINX Plus
    运行设置-x \
    && apt-get 更新 \
    && apt-get install –no-install-recommends –no-install-suggests -y apt-transport-https ca-certificates gnupg1 \
    && \
    NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
    发现=”; \
    对于 \ 中的服务器
    ha.pool.sks-keyservers.net \
    hkp://keyserver.ubuntu.com:80 \
    hkp://p80.pool.sks-keyservers.net:80 \
    pgp.mit.edu \
    ;做 \
    echo “正在从 $server 获取 GPG 密钥 $NGINX_GPGKEY”; \
    apt-key adv –keyserver “$server” –keyserver-options timeout=10 –recv-keys “$NGINX_GPGKEY” && 发现=yes && 中断; \
    完毕; \
    test -z “$found” && echo >&2 “错误:无法获取 GPG 密钥 $NGINX_GPGKEY” && exit 1; \
    echo “获取::https::plus-pkgs.nginx.com::Verify-Peer \”true\”;” >> /etc/apt/apt.conf.d/90nginx \
    && echo “获取::https::plus-pkgs.nginx.com::Verify-Host \”true\”;” >> /etc/apt/apt.conf.d/90nginx \
    && echo “获取::https::plus-pkgs.nginx.com::SslCert \”/etc/ssl/nginx/nginx-repo.crt\”;” >> /etc/apt/apt.conf.d/90nginx \
    && echo “获取::https::plus-pkgs.nginx.com::SslKey \”/etc/ssl/nginx/nginx-repo.key\”;” >> /etc/apt/apt.conf.d/90nginx \
    && echo “获取::https::plus-pkgs.nginx.com::User-Agent \”k8s-ic-$IC_VERSION-apt\”;” >> /etc/apt/apt.conf.d/90nginx \
    && printf “deb https://plus-pkgs.nginx.com/debianstretch nginx-plus\n” > /etc/apt/sources.list.d/nginx-plus.list \
    && apt-get update && apt-get install -y nginx-plus=${NGINX_PLUS_VERSION} nginx-plus-module-njs \
    && apt-get 删除 –purge –auto-remove -y gnupg1 \
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /etc/ssl/nginx \
    && rm /etc/apt/apt.conf.d/90nginx /etc/apt/sources.list.d/nginx-plus.list# 将NGINX访问和错误日​​志转发到Ingress的stdout和stderr
    # 控制器进程
    运行 ln -sf /proc/1/fd/1 /var/log/nginx/access.log \
    && ln -sf /proc/1/fd/1 /var/log/nginx/stream-access.log \
    && ln -sf /proc/1/fd/1 /var/log/nginx/oidc_auth.log \
    && ln -sf /proc/1/fd/2 /var/log/nginx/error.log \
    && ln -sf /proc/1/fd/2 /var/log/nginx/oidc_error.log

    曝光 80 443

    复制 nginx-ingress 内部/configs/version1/nginx-plus.ingress.tmpl 内部/configs/version1/nginx-plus.tmpl 内部/configs/version2/nginx-plus.virtualserver.tmpl /

    运行 rm /etc/nginx/conf.d/* \
    && mkdir -p /etc/nginx/secrets

    # 取消注释下面的行以将 default.pem 文件添加到图像中
    # 并将其用作默认服务器的证书和密钥
    # 添加default.pem /etc/nginx/secrets/default

    入口点 [“/nginx-ingress”][/config]

    我们用 1.5.0-oidc 标记 Dockerfile,并将镜像推送到 Docker Hub 上名为 nginx-plus:1.5.0-oidc 的私有存储库。我们的私人仓库称为 magicyak,但我们会提醒您根据需要替换您的私人存储库的名称。

    为了为自定义 Docker 映像准备 Kubernetes 节点,我们在每个节点上运行以下命令。这使得 Kubernetes 能够将 Ingress 资源放置在其选择的节点上。 (您也可以仅在一个节点上运行命令,然后指示 Ingress 资源在该节点上专门运行。)在最后一个命令中,将您的私有存储库的名称替换为 magicyak:

    [终端]$ sudo groupadd docker
    $ sudo usermod -aG docker $USER
    $ docker login # 这会提示您输入 Docker 用户名和密码
    $ docker pull magicyak/nginx-plus:1.5.0-oidc[/terminal]

    此时 Kubernetes 节点正在运行。

    为了使用 Kubernetes 仪表板,我们运行以下命令。第一个在本地计算机(本例中为 MacBook)上启用 kubectl。第二个返回仪表板的 URL,第三个返回我们访问仪表板所需的令牌(我们将粘贴将其放入仪表板登录页面上的令牌字段中)。

    [终端]$ cp inventory/mycluster/artifacts/admin.conf ~/.kube/config
    $ kubectl cluster-info # 为我们提供仪表板 URL
    $ kubectl -n kube-system 描述秘密 \
    `kubectl -n kube-system 获取秘密 | awk ‘/clusterrole-aggregation-controller/ {print $1}’` \
    | awk ‘/token:/ {print $2}'[/terminal]

    安装和自定义 NGINX Plus Ingress 控制器

    现在,我们在 Kubernetes 集群中安装 NGINX Plus Ingress Controller,并通过将 Azure AD 生成的 ID 和密钥合并到从 OpenID Connect 身份提供商获取凭据中来自定义 OIDC 的配置。

    克隆 NGINX Plus Ingress 控制器存储库

    我们首先克隆 kubernetes-ingress GitHub 存储库并将目录更改为部署子目录。然后我们运行 kubectl 命令来创建所需的资源:命名空间和服务帐户,默认值服务器密钥、自定义资源定义和基于角色的访问控制 (RBAC)。

    [终端]$ git clone https://github.com/nginxinc/kubernetes-ingress
    $ cd kubernetes-ingress/deployments
    $ kubectl create -f common/ns-and-sa.yaml
    $ kubectl create -f common/default-server-secret.yaml
    $ kubectl create -f common/custom-resource-definitions.yaml
    $ kubectl create -f rbac/rbac.yaml[/terminal]

    创建 NGINX ConfigMap

    现在,我们将 common/nginx-config.yaml 文件的内容替换为以下内容,即启用 njs 模块并包含 OIDC 配置的 ConfigMap。

    [配置滚动=“true”]种类:ConfigMap
    api版本:v1
    元数据:
    名称:nginx-config
    命名空间:nginx-ingress
    数据:
    #外部状态地址:172.16.186.101
    主要片段: |
    load_module模块/ngx_http_js_module.so;
    入口模板: |
    # {{.Ingress.Namespace}}/{{.Ingress.Name}} 的配置
    {{- 如果索引 $.Ingress.Annotations “custom.nginx.org/enable-oidc”}}
    {{$oidc := 索引 $.Ingress.Annotations “custom.nginx.org/enable-oidc”}}
    {{- if eq $oidc “True”}}
    {{- $kv_zone_size := 索引 $.Ingress.Annotations “custom.nginx.org/keyval-zone-size”}}
    {{- $refresh_time := index $.Ingress.Annotations “custom.nginx.org/refresh-token-timeout”}}
    {{- $session_time := index $.Ingress.Annotations “custom.nginx.org/session-token-timeout”}}
    {{- 如果不是 $kv_zone_size}}{{$kv_zone_size = “1M”}}{{end}}
    {{- 如果不是 $refresh_time}}{{$refresh_time = “8h”}}{{end}}
    {{- 如果不是 $session_time}}{{$session_time = “1h”}}{{end}}
    keyval_zone zone=opaque_sessions:{{$kv_zone_size}} state=/var/lib/nginx/state/opaque_sessions.json timeout={{$session_time}};
    keyval_zone zone=refresh_tokens:{{$kv_zone_size}} state=/var/lib/nginx/state/refresh_tokens.json timeout={{$refresh_time}};
    keyval $cookie_auth_token $session_jwt zone=opaque_sessions;
    keyval $cookie_auth_token $refresh_token zone=refresh_tokens;
    keyval $request_id $new_session区域=opaque_sessions;
    keyval $request_id $new_refresh zone=refresh_tokens;

    proxy_cache_path /var/cache/nginx/jwklevels=1keys_zone=jwk:64k max_size=1m;

    地图 $refresh_token $no_refresh {
    “”1;
    “-”1;
    默认 0;
    }

    log_format main_jwt ‘$remote_addr $jwt_claim_sub $remote_user [$time_local] “$request” $status ‘
    ‘$body_bytes_sent“$http_referer”“$http_user_agent”“$http_x_forwarded_for”’;

    js_include conf.d/openid_connect.js;
    js_set $requestid_hash hashRequestId;
    auth_jwt_claim_set $jwt_audience aud; # 如果 aud 是一个数组
    {{结束}}{{结束 -}}
    {{范围 $upstream := .Upstreams}}
    上游 {{$upstream.Name}} {
    区域 {{$upstream.Name}} 256k;
    {{如果$upstream.LBMethod}}{{$upstream.LBMethod}};{{end}}
    {{范围 $server := $upstream.UpstreamServers}}
    服务器 {{$server.Address}}:{{$server.Port}} max_fails={{$server.MaxFails}} failure_timeout={{$server.Fail超时}}
    {{- if $server.SlowStart}} Slow_start={{$server.SlowStart}}{{end}}{{if $server.Resolve}} 解析{{end}};{{end}}
    {{如果$upstream.StickyCookie}}
    粘性 cookie {{$upstream.StickyCookie}};
    {{结尾}}
    {{if $.Keepalive}}保持活动 {{$.Keepalive}};{{end}}
    {{- 如果 $upstream.UpstreamServers -}}
    {{- if $upstream.Queue}}
    队列{{$upstream.Queue}}超时={{$upstream.QueueTimeout}}s;
    {{- 结尾 -}}
    {{- 结尾}}
    }
    {{- 结尾}}

    {{范围 $server := .Servers}}
    服务器 {
    {{如果不是 $server.GRPCOnly}}
    {{范围 $port := $server.Ports}}
    监听 {{$port}}{{if $server.ProxyProtocol}} proxy_protocol{{end}};
    {{- 结尾}}
    {{结尾}}
    {{如果$server.SSL}}
    {{- 范围 $port := $server.SSLPorts}}
    监听 {{$port}} ssl{{if $server.HTTP2}} http2{{end}}{{if $server.ProxyProtocol}} proxy_protocol{{end}};
    {{- 结尾}}
    ssl_certificate {{$server.SSLCertificate}};
    ssl_certificate_key {{$server.SSLCertificateKey}};
    {{如果$server.SSLCiphers}}
    ssl_ciphers {{$server.SSLCiphers}};
    {{结尾}}
    {{结尾}}
    {{范围 $setRealIPFrom := $server.SetRealIPFrom}}
    set_real_ip_from {{$setRealIPFrom}};{{end}}
    {{if $server.RealIPHeader}}real_ip_header {{$server.RealIPHeader}};{{end}}
    {{if $server.RealIPRecursive}}real_ip_recursive on;{{end}}

    server_tokens “{{$server.ServerTokens}}”;

    server_name {{$server.Name}};

    status_zone {{$server.StatusZone}};

    {{如果不是 $server.GRPCOnly}}
    {{范围 $proxyHideHeader := $server.ProxyHideHeaders}}
    proxy_hide_header {{$proxyHideHeader}};{{end}}
    {{范围 $proxyPassHeader := $server.ProxyPassHeaders}}
    proxy_pass_header {{$proxyPassHeader}};{{end}}
    {{结尾}}

    {{如果$server.SSL}}
    {{如果不是 $server.GRPCOnly}}
    {{- 如果 $server.HSTS}}
    放$hsts_header_val “”;
    proxy_hide_header 严格传输安全;
    {{- 如果 $server.HSTSBehindProxy}}
    如果($http_x_forwarded_proto = ‘https’){
    {{别的}}
    如果($https = 上){
    {{- 结尾}}
    设置 $hsts_header_val “max-age={{$server.HSTSMaxAge}}; {{if $server.HSTSIncludeSubdomains}}includeSubDomains; {{end}}预加载”;
    }

    add_header 严格传输安全“$hsts_header_val”始终;
    {{结尾}}

    {{- 如果 $server.SSLRedirect}}
    如果($方案= http){
    返回 301 https://$host:{{index $server.SSLPorts 0}}$request_uri;
    }
    {{- 结尾}}
    {{结尾}}
    {{- 结尾}}

    {{- 如果 $server.RedirectToHTTPS}}
    如果($http_x_forwarded_proto = ‘http’){
    返回 301 https://$host$request_uri;
    }
    {{- 结尾}}

    {{与 $jwt := $server.JWTAuth}}
    auth_jwt_key_file {{$jwt.Key}};
    auth_jwt “{{.Realm}}”{{if $jwt.Token}} 令牌={{$jwt.Token}}{{end}};

    {{- 如果 $jwt.RedirectLocationName}}
    error_page 401 {{$jwt.RedirectLocationName}};
    {{结尾}}
    {{结尾}}

    {{- 如果 $server.ServerSnippets}}
    {{范围 $value := $server.ServerSnippets}}
    {{$值}}{{结束}}
    {{- 结尾}}

    {{- 范围 $healthCheck := $server.HealthChecks}}
    位置@hc-{{$healthCheck.UpstreamName}} {
    {{- 范围 $name, $header := $healthCheck.Headers}}
    proxy_set_header {{$name}} “{{$header}}”;
    {{- 结尾 }}
    proxy_connect_timeout {{$healthCheck.TimeoutSeconds}}s;
    proxy_read_timeout {{$healthCheck.TimeoutSeconds}}s;
    proxy_send_timeout {{$healthCheck.TimeoutSeconds}}s;
    proxy_pass {{$healthCheck.Scheme}}://{{$healthCheck.UpstreamName}};
    health_check {{if $healthCheck.Mandatory}}强制 {{end}}uri={{$healthCheck.URI}} 间隔=
    {{- $healthCheck.Interval}}s失败={{$healthCheck.Fails}} 通过={{$healthCheck.P​​asses}};
    }
    {{结尾 -}}

    {{- 范围 $location := $server.JWTRedirectLocations}}
    位置 {{$location.Name}} {
    内部的;
    返回 302 {{$location.LoginURL}};
    }
    {{结尾 -}}

    {{- 如果索引 $.Ingress.Annotations “custom.nginx.org/enable-oidc”}}
    {{- $oidc_resolver := 索引 $.Ingress.Annotations “custom.nginx.org/oidc-resolver-address”}}
    {{- 如果不是 $oidc_resolver}}{{$oidc_resolver = “8.8.8.8”}}{{end}}
    解析器 {{$oidc_resolver}};
    子请求输出缓冲区大小 32k;

    {{- $oidc_jwt_keyfile := 索引 $.Ingress.Annotations “custom.nginx.org/oidc-jwt-keyfile”}}
    {{- $oidc_logout_redirect := 索引 $.Ingress.Annotations “custom.nginx.org/oidc-logout-redirect”}}
    {{- $oidc_authz_endpoint := 索引 $.Ingress.Annotations “custom.nginx.org/oidc-authz-endpoint”}}
    {{- $oidc_token_endpoint := 索引 $.Ingress.Annotations“custom.nginx.org/oidc-token-endpoint”}}
    {{- $oidc_client := 索引 $.Ingress.Annotations “custom.nginx.org/oidc-client”}}
    {{- $oidc_client_secret := 索引 $.Ingress.Annotations “custom.nginx.org/oidc-client-secret”}}
    {{ $oidc_hmac_key := 索引 $.Ingress.Annotations “custom.nginx.org/oidc-hmac-key”}}
    设置 $oidc_jwt_keyfile “{{$oidc_jwt_keyfile}}”;
    设置 $oidc_logout_redirect “{{$oidc_logout_redirect}}”;
    设置 $oidc_authz_endpoint “{{$oidc_authz_endpoint}}”;
    设置 $oidc_token_endpoint “{{$oidc_token_endpoint}}”;
    设置 $oidc_client “{{$oidc_client}}”;
    设置 $oidc_client_secret “{{$oidc_client_secret}}”;
    设置 $oidc_hmac_key “{{$oidc_hmac_key}}”;
    {{结尾 -}}

    {{范围 $location := $server.Locations}}
    位置 {{$location.Path}} {
    {{与 $location.MinionIngress}}
    # 小黄人的位置 {{$location.MinionIngress.Namespace}}/{{$location.MinionIngress.Name}}{{结尾}}
    {{如果$location.GRPC}}
    {{如果不是 $server.GRPCOnly}}
    error_page 400 @grpcerror400;
    error_page 401 @grpcerror401;
    error_page 403 @grpcerror403;
    error_page 404 @grpcerror404;
    error_page 405 @grpcerror405;
    error_page 408 @grpcerror408;
    error_page 414 @grpcerror414;
    error_page 426 @grpcerror426;
    error_page 500 @grpcerror500;
    error_page 501 @grpcerror501;
    error_page 502 @grpcerror502;
    error_page 503 @grpcerror503;
    error_page 504 @grpcerror504;
    {{结尾}}

    {{- if $location.LocationSnippets}}
    {{范围 $value := $location.LocationSnippets}}
    {{$值}}{{结束}}
    {{- 结尾}}

    {{with $jwt := $location.JWTAuth}}
    auth_jwt_key_file {{$jwt.Key}};
    auth_jwt “{{.Realm}}”{{if $jwt.Token}} 令牌={{$jwt.令牌}}{{结束}};
    {{结尾}}

    grpc_connect_timeout {{$location.ProxyConnectTimeout}};
    grpc_read_timeout {{$location.ProxyReadTimeout}};
    grpc_set_header 主机 $host;
    grpc_set_header X-真实IP $remote_addr;
    grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    grpc_set_header X-Forwarded-Host $host;
    grpc_set_header X-转发端口 $server_port;
    grpc_set_header X-Forwarded-Proto $scheme;

    {{- 如果 $location.ProxyBufferSize}}
    grpc_buffer_size {{$location.ProxyBufferSize}};
    {{- 结尾}}

    {{如果$location.SSL}}
    grpc_pass grpcs://{{$location.Upstream.Name}}
    {{别的}}
    grpc_pass grpc://{{$location.Upstream.Name}};
    {{结尾}}
    {{别的}}
    proxy_http_版本 1.1;
    {{如果$location.Websocket}}
    proxy_set_header 升级 $http_upgrade;
    proxy_set_header 连接 $connection_upgrade;
    {{- 别的}}
    {{- if $.Keepalive}}proxy_set_header 连接 “”;{{end}}
    {{- 结尾}}

    {{- if $location.LocationSnippets}}
    {{范围 $value := $location.LocationSnippets}}
    {{$值}}{{结束}}
    {{- 结尾}}

    {{ 与 $jwt := $location.JWTAuth }}
    auth_jwt_key_file {{$jwt.Key}};
    auth_jwt “{{.Realm}}”{{if $jwt.Token}} token={{$jwt.Token}}{{end}};
    {{if $jwt.RedirectLocationName}}
    error_page 401 {{$jwt.RedirectLocationName}};
    {{结尾}}
    {{结尾}}

    {{- 如果索引 $.Ingress.Annotations “custom.nginx.org/enable-oidc”}}
    auth_jwt “” 令牌=$session_jwt;
    auth_jwt_key_request /_jwks_uri;
    error_page 401 @oidc_auth;
    {{结尾}}

    proxy_connect_timeout {{$location.ProxyConnectTimeout}};
    proxy_read_timeout {{$location.ProxyReadTimeout}};
    client_max_body_size {{$location.ClientMaxBodySize}};
    proxy_set_header 主机 $host;
    proxy_set_header X-真实IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-转发端口 $server_port;
    proxy_set_header X-Forwarded-Proto {{if $server.RedirectToHTTPS}}https{{else}}$scheme{{end}};
    proxy_buffering {{if $location.ProxyBuffering}}开启{{else}}关闭{{end}};
    {{- 如果 $location.ProxyBuffers}}
    proxy_buffers {{$location.ProxyBuffers}};
    {{- 结尾}}
    {{- 如果 $location.ProxyBufferSize}}
    proxy_buffer_size {{$location.ProxyBufferSize}};
    {{- 结尾}}
    {{- 如果 $location.ProxyMaxTempFileSize}}
    proxy_max_temp_file_size {{$location.ProxyMaxTempFileSize}};
    {{- 结尾}}{{如果$location.SSL}}
    proxy_pass https://{{$location.Upstream.Name}}{{$location.Rewrite}};
    {{别的}}
    proxy_pass http://{{$location.Upstream.Name}}{{$location.Rewrite}};
    {{结尾}}
    {{结尾}}
    }{{结尾}}
    {{如果$server.GRPCOnly}}
    error_page 400 @grpcerror400;
    error_page 401 @grpcerror401;
    error_page 403 @grpcerror403;
    error_page 404 @grpcerror404;
    error_page 405 @grpcerror405;
    error_page 408 @grpcerror408;
    error_page 414 @grpcerror414;
    error_page 426 @grpcerror426;
    error_page 500 @grpcerror500;
    error_page 501 @grpcerror501;
    error_page 502 @grpcerror502;
    error_page 503 @grpcerror503;
    error_page 504 @grpcerror504;
    {{结尾}}
    {{如果$server.HTTP2}}
    位置 @grpcerror400 { default_type 应用程序/grpc;返回 400 “\n”; }
    位置@grpcerror401 {default_type应用程序/grpc;返回401“\n”;}
    位置@grpcerror403 {default_type应用程序/grpc;第403章}
    位置@grpcerror404 {default_type应用程序/grpc;返回 404 “\n”; }
    位置@grpcerror405 {default_type应用程序/grpc;返回 405 “\n”; }
    位置@grpcerror408 {default_type应用程序/grpc;第408章}
    位置 @grpcerror414 { default_type 应用程序/grpc;第414章}
    位置 @grpcerror426 { default_type 应用程序/grpc;第426章}
    位置 @grpcerror500 { default_type 应用程序/grpc;返回 500 “\n”; }
    位置@grpcerror501 {default_type应用程序/grpc;返回 501 “\n”; }
    位置@grpcerror502 {default_type应用程序/grpc;第502章}
    位置@grpcerror503 {default_type应用程序/grpc;第503章}
    位置@grpcerror504 {default_type应用程序/grpc;第504章}
    {{结尾}}
    {{- 如果索引 $.Ingress.Annotations “custom.nginx.org/enable-oidc”-}}
    包括conf.d/openid_connect.server_conf;
    {{- 结尾}}
    {{结束}}[/配置]

    现在我们在 Kubernetes 中部署 ConfigMap,并将目录更改回 kubernetes-ingress。

    [终端]$ kubectl create -f common/nginx-config.yaml
    $ cd ..[/终端]

    将 OpenID Connect 纳入 NGINX Plus Ingress 控制器

    编辑器 – 正如博客开头所述,本博客中描述的过程不适用于 NGINX Plus R22 及更高版本,因为使用 OIDC 对 NGINX Plus 身份验证的参考实现进行了增强。有关替代方法,请参阅 Tom 的 Ansible 角色,该角色有助于生成与 NGINX Plus R22 和 NGINX Ingress Controller 配合使用的配置。

    由于我们使用 OIDC 资源,因此我们利用了 GitHub 上 NGINX 提供的 OIDC 参考实现。在我们现有的 kubernetes-ingress 存储库中克隆 nginx-openid-connect 存储库后,我们从 o 创建 ConfigMappenid-connect.js 和 openid-connect.server-conf 文件。

    [终端]$ git clone https://github.com/nginxinc/nginx-openid-connect
    $ cd nginx-openid-connect
    $ kubectl create configmap -n nginx-ingress openid-connect.js –from-file=openid_connect.js
    $ kubectl create configmap -n nginx-ingress openid-connect.server-conf –from-file=openid_connect.server_conf[/terminal]

    现在,我们将以下指令添加到 kubernetes-ingress 存储库的部署/部署子目录中的现有 nginx-plus-ingress.yaml 文件中,将这两个文件作为 ConfigMap 类型的 Kubernetes 卷合并到我们的 Ingress 控制器部署中:

    [配置]卷:
    – 名称:openid-connect-js
    配置映射:
    名称:openid-connect.js
    – 名称:openid-connect-server-conf
    配置映射:
    名称:openid-connect.server-conf[/config]

    我们还将以下指令添加到 nginx-plus-ingress.yaml 中,以使文件可以在部署的 /etc/nginx/conf.d 目录中访问:

    [配置]体积安装:
    – 名称:openid-connect-js
    挂载路径:/etc/nginx/conf.d/openid_connect.js
    子路径:openid_connect.js
    – 名称:openid-connect-server-conf
    挂载路径:/etc/nginx/conf.d/openid_connect.server_conf
    子路径:openid_connect.server_conf[/config]

    这是我们部署的完整 nginx-plus-ingress.yaml 文件。如果将其用作您自己的部署的基础,请将 magicyak 替换为您的私有注册表的名称。

    [配置滚动=“true”]api版本:apps/v1
    种类:部署
    元数据:
    名称:nginx-ingress
    命名空间:nginx-ingress
    标签:
    应用程序:nginx-ingress
    规格:
    副本:1
    选择器:
    匹配标签:
    应用程序:nginx-ingress
    模板:
    元数据:
    标签:
    应用程序:nginx-ingress
    #注释:
    # prometheus.io/scrape:“真”
    # prometheus.io/端口:“9113”
    规格:
    容器:
    – 图片:magicyak/nginx-plus:1.5.0-oidc
    imagePullPolicy:IfNotPresent
    名称:nginx-plus-ingress
    参数:
    –nginx-plus
    – -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
    – -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
    –报告入口状态
    #- -v=3 # 启用广泛的日志记录。对于故障排除很有用。
    #–external-service=nginx-ingress
    #–启用领导者选举
    #–启用普罗米修斯指标
    #–启用自定义资源
    环境:
    – 名称:POD_NAMESPACE
    值来自:
    字段参考:
    字段路径:metadata.namespace
    – 名称:POD_NAME
    值来自:
    字段参考:
    字段路径:元数据.名称
    端口:
    – 名称:http
    集装箱端口:80
    – 名称:https
    容器端口:443
    #- 名称:普罗米修斯
    # 容器端口:9113
    体积安装:
    – 名称:openid-connect-js
    挂载路径:/etc/nginx/conf.d/openid_connect.js
    子路径:openid_connect.js
    – 名称:openid-connect-server-conf
    莫untPath:/etc/nginx/conf.d/openid_connect.server_conf
    子路径:openid_connect.server_conf
    服务帐户名称:nginx-ingress
    卷:
    – 名称:openid-connect-js
    配置映射:
    名称:openid-connect.js
    – 名称:openid-connect-server-conf
    配置映射:
    名称:openid-connect.server-conf[/config]

    创建 Kubernetes 服务

    我们还需要通过在 kubernetes-ingress 存储库的部署/服务子目录中创建一个名为 nginx-plus-service.yaml 的新文件来定义 Kubernetes 服务。我们将ExternalIPs字段设置为我们在安装和配置Kubernetes中分配给node1的别名IP地址(172.16.186.100),但您可以使用NodePorts或其他选项。

    [配置]api版本:v1
    种类: 服务
    元数据:
    名称:nginx-ingress
    命名空间:nginx-ingress
    标签:
    svc: nginx-ingress
    规格:
    类型:集群IP
    集群IP:
    外部IP:
    – 172.16.186.100
    端口:
    – 名称:http
    端口:80
    目标端口:http协议:TCP
    – 名称:https
    端口:443
    目标端口:https
    协议:TCP
    选择器:
    应用程序:nginx-ingress[/config]

    部署入口控制器

    所有 YAML 文件就位后,我们运行以下命令在 Kubernetes 中部署 Ingress 控制器和服务资源:

    [终端]$ cd ../deployments
    $ kubectl create -f 部署/nginx-plus-ingress.yaml
    $ kubectl create -f service/nginx-plus-service.yaml
    $ cd ..[/终端]

    此时,我们的 Ingress 控制器已安装,我们可以专注于创建我们使用 OIDC 身份验证的示例资源。

    设置示例应用程序以使用 OpenID Connect

    为了测试我们的 OIDC 身份验证设置,我们使用一个名为咖啡馆的非常简单的应用程序,它具有茶和咖啡服务端点。它包含在 GitHub 上 kubernetes-ingress 存储库的 Examples/complete-example 目录中,您可以在 NGINX 和 NGINX Plus Ingress C 中阅读有关它的更多信息我们博客上的 Kubernetes 负载平衡控制器。

    然而,我们需要对示例应用程序进行一些修改 – 具体来说,我们需要将从 Azure AD 获取的值插入到应用程序的 YAML 文件中,即 Examples/complete-example 目录中的 Cafe-ingress.yaml。

    我们正在进行两组更改,如下面的完整文件所示:

  • 我们正在添加注释部分。下面的文件使用 {client_key}、{tenant_key} 和 {client_secret} 变量来表示从 IdP 获取的值。为了更轻松地跟踪我们所引用的值,我们在列表中指定了在从 OpenID Connect 身份提供程序获取凭据的指示步骤中从 Azure AD 获取的文字值。创建自己的部署时,请替换从 Azure AD(或其他 IdP)获取的值。

    • {client_key} – Azure AD 确认页面上的应用程序(客户端)ID 字段中的值。对于我们的部署,它是 a2b20239-2dce-4306-a385-ac9xxx,如第 6 步中所报告。
    • {tenant_key} – Azure AD 确认页面上的目录(租户)ID 字段中的值。对于我们的部署,它是 dd3dfd2f-6a3b-40d1-9be0-bf8xxx,如第 6 步中所示。
    • {client_secret} – Azure AD 中客户端密钥部分中值的 URL 编码版本。对于我们的部署,它是 kn_3VLh%5D1I3ods%2A%5BDDmMxNmg8xxx,如步骤 9 中所述。

    此外,请注意,custom.nginx.org/oidc-hmac-key 字段中的值只是一个示例。替换您自己的唯一值,以确保随机数值不可预测。

  • 我们将主机和主机字段中的值更改为cafe.nginx.net,并将该域的条目添加到四个 Kubernetes 节点中每个节点上的 /etc/hosts 文件中,指定来自nginx-plus-service.yaml 中的 ClusterIP 字段。在我们的部署中,我们设置了这个安装和配置 Kubernetes 中的 172.16.186.100。

  • [config]api版本:extensions/v1beta1
    种类: 入口
    元数据:
    名称: 咖啡馆入口
    注释:
    custom.nginx.org/enable-oidc:“正确”
    custom.nginx.org/keyval-zone-size: “1m” # (默认 1m)
    custom.nginx.org/refresh-token-timeout: “8h” # (默认 8h)
    custom.nginx.org/session-token-timeout: “1h” # (默认 1h)
    custom.nginx.org/oidc-resolver-address: “8.8.8.8” # (默认 8.8.8.8)
    custom.nginx.org/oidc-jwt-keyfile:“https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys”
    custom.nginx.org/oidc-logout-redirect:“https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout”
    custom.nginx.org/oidc-authz-endpoint:“https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize”
    custom.nginx.org/oidc-token-endpoint:“https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token”
    custom.nginx.org/oidc-client:“{client_key}”
    custom.nginx.org/oidc-客户端秘密:“{client_secret}”
    custom.nginx.org/oidc-hmac-key:“vC5FabzvYvFZFBzxtRCYDYX+”
    规格:
    :
    – 主持人:
    – 咖啡馆.nginx.net
    秘密名称:咖啡馆的秘密
    规则:
    – 主机:cafe.nginx.net
    http:
    路径:
    – 路径:/茶
    后端:
    服务名称:tea-svc
    服务端口:80
    – 路径:/咖啡
    后端:
    服务名称:coffee-svc
    服务端口:80[/config]

    我们现在在 Kubernetes 中创建咖啡馆资源:

    [终端]$ cd 示例/完整示例
    $ kubectl create -f Cafe-secret.yaml
    $ kubectl 创建 -f Cafe.yaml
    $ kubectl create -f Cafe-ingress.yaml
    $ cd ../..[/终端]

    为了验证 OIDC 身份验证过程是否正常工作,我们在浏览器中导航到 http://cafe.nginx.com/tea。它会提示我们输入登录凭据,对我们进行身份验证,并显示茶服务生成的一些基本信息。有关示例,请参阅用于 Kubernetes 负载平衡的 NGINX 和 NGINX Plus 入口控制器。 –>


    评论

    发表回复

    您的电子邮箱地址不会被公开。 必填项已用 * 标注