0

我正在尝试为我的 Helm 图表中的所有入口资源生成 TLS 证书。我的掌舵图包含一个具有多个后端的应用程序,因此我的 Values.yaml 的结构如下:

backend1:
  ingress:
    host: testing.app.com
    tls:
      - secretName: my-tls-cert
        hosts:
          - testing.app.com
backend2:
  ingress:
    host: testing.app.com
    tls:
      - secretName: idp-cts-cert
        hosts:
          - idp-cts
db
  creds: ""

serviceName: ""

请注意,地图和字符串值混合在一起。我的目标是使用我编写的实用程序模板来调用genSignedCert并生成一个 TLS 证书,该证书将主机列为 CN 或备用名称:

{{/*
Return a self-signed TLS certificate
{{ include "common.certs.ingress-tls" .hosts  }}
*/}}
{{- define "common.certs.gen-cert" -}}
{{- $hostlist := toStrings . -}}
{{- $cn := (first $hostlist) -}}
{{- $altnames := uniq (rest $hostlist) -}}
{{- $ca := genCA "idp-ca" 365 -}}
{{- $cert := genSignedCert $cn nil $altnames 365 $ca -}}
tls.crt: {{ $cert.Cert | b64enc }}
tls.key: {{ $cert.Key | b64enc }}
{{- end -}} 

我尝试过迭代这些值,但我想不出可行的代码来做到这一点。

Edit1:我知道使用自签名证书的安全隐患。糟糕的 values.yaml 结构继承自这是一个伞形图表,每个支持的图表也是它自己的图表。可能需要重构图表结构,但我想先用尽所有选项。

4

1 回答 1

1

考虑在 Helm 之外生成 TLS 证书,并通过值将其注入(或将其组件直接存储在 Secret 中)。这避免了一些复杂的代码。但是,还有一个更严重的问题:每次调用genCAgenSignedCert都会创建一个新证书,因此每次升级时都会获得不同的证书,就此而言,如果您为每个 Ingress 对象调用此模板一次,则每个都将拥有不同的证书。


values.yaml稍微调整一下对这个问题有帮助。代码很难判断这backend1是后端规范,但serviceName不是。如果您只有一个列表,backends则变得更容易:

backends:
  - ingress:
      host: testing.app.com
      ...
  - ingress:
      host: testing.app.com
      ...

然后,您将遇到 Helm 模板作为一种功能齐全的编程语言的一些限制。模板只返回字符串,因此您不能编写返回列表的模板。您不能将函数作为参数传递给模板,因此您不能编写通用的map(在有限的情况下,您可以传递模板名称和include它)。

您可以做的是编写一个递归函数,将部分列表向前传递到下一次迭代,然后在完成后调用最终生成器。在 Python 中,我们可能会这样写:

def generateCertificate(backends, tls, hosts):
  # If `tls` is non-empty, take the first item from it and add its
  # hosts to the `hosts` list; then recurse with the same backend
  # list, the remaining `tls` items, and the updated `hosts`:
  if len(tls) > 0:
    return generateCertificate(backends, tls[1:], hosts + tls[0].hosts)
  # If `tls` is empty but `backends` is non-empty, take the first
  # backend, and recurse with the remaining `backends`, the `tls` items
  # from the selected backend, and the same `hosts`:
  else if len(backends) > 0:
    return generateCertificate(backends[1:], backends[0].tls, hosts)
  # If `tls` and `backends` are both empty, we're done
  else:
    return buildTheCertificate(hosts)

certificate = generateCertificate(values.backends, [], [])

我们可以将此逻辑转换为 Go 模板:

{{/* Emit a TLS certificate given the list of backends.  The
parameter is a dictionary with keys `backends`, `tls`, and `hosts`. */}}
{{- define "common.certs.gen-cert" -}}
{{- if .tls -}}
  {{- include "common.certs.gen-cert" (dict "backends" .backend "tls" (last .tls) "hosts" (concat .hosts (head .tls).hosts)) -}}
{{- else if .backends -}}
  {{- include "common.certs.gen-cert" (dict "backends" (tail .backends) "tls" (head .backends).tls "hosts" .hosts) -}}
{{- else -}}
  {{- include "common.certs.gen-cert-hosts" .hosts -}}
{{- end -}}
{{- end -}}

{{/* Actually generate a TLS certificate from a list of host names.
Note, the certificate will be regenerated on every call.  The
single parameter is a list of names. */}}
{{- define "common.certs.gen-cert-hosts" -}}
{{- $cn := first . -}}
{{- $altnames := rest . | uniq -}}
{{- $ca := genCA "idp-ca" 365 -}}
{{- $cert := genSignedCert $cn nil $altnames 365 $ca -}}
tls.crt: {{ $cert.Cert | b64enc }}
tls.key: {{ $cert.Key | b64enc }}
{{- end -}}

{{- include "common.certs.gen-cert" (dict "backends" .Values.backends) -}}

这是足够复杂的代码,可能值得对其进行单元测试。将其设置为练习。Helm 在这里没有任何原生支持。

于 2021-08-10T11:59:06.310 回答