Merge remote-tracking branch 'origin/master' into azureendpoint
Signed-off-by: Stuart Hu <shijiehu@improbable.io>
This commit is contained in:
commit
9bdb275f49
1
.bazelversion
Normal file
1
.bazelversion
Normal file
@ -0,0 +1 @@
|
||||
0.25.3
|
||||
22
BUILD.bazel
22
BUILD.bazel
@ -1,31 +1,19 @@
|
||||
load("@io_bazel_rules_docker//container:container.bzl", "container_bundle")
|
||||
load("@io_bazel_rules_docker//contrib:push-all.bzl", "docker_push")
|
||||
load("//hack:def.bzl", "multiarch_bundle")
|
||||
|
||||
# export WORKSPACE so workspace_binary rules can be used outside the root
|
||||
exports_files(["WORKSPACE"])
|
||||
|
||||
# gazelle:proto disable_global
|
||||
|
||||
# this target creates targets:
|
||||
# 'images' (all arch and os)
|
||||
# 'images.linux-amd64'
|
||||
# 'images.linux-arm64'
|
||||
# 'images.linux-arm'
|
||||
multiarch_bundle(
|
||||
container_bundle(
|
||||
name = "images",
|
||||
arch = [
|
||||
"amd64",
|
||||
"arm64",
|
||||
"arm",
|
||||
],
|
||||
images = {
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-controller-{arch}:{STABLE_DOCKER_TAG}": "//cmd/controller:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-acmesolver-{arch}:{STABLE_DOCKER_TAG}": "//cmd/acmesolver:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-webhook-{arch}:{STABLE_DOCKER_TAG}": "//cmd/webhook:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-cainjector-{arch}:{STABLE_DOCKER_TAG}": "//cmd/cainjector:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-controller:{STABLE_DOCKER_TAG}": "//cmd/controller:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-acmesolver:{STABLE_DOCKER_TAG}": "//cmd/acmesolver:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-webhook:{STABLE_DOCKER_TAG}": "//cmd/webhook:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-cainjector:{STABLE_DOCKER_TAG}": "//cmd/cainjector:image",
|
||||
},
|
||||
os = ["linux"],
|
||||
)
|
||||
|
||||
docker_push(
|
||||
|
||||
1
Makefile
1
Makefile
@ -142,6 +142,7 @@ images:
|
||||
bazel run //hack/release -- \
|
||||
--repo-root "$$(pwd)" \
|
||||
--images \
|
||||
--images.export=true \
|
||||
--images.goarch="amd64" \
|
||||
--app-version="$(APP_VERSION)" \
|
||||
--docker-repo="$(DOCKER_REPO)"
|
||||
|
||||
58
WORKSPACE
58
WORKSPACE
@ -1,3 +1,8 @@
|
||||
workspace(
|
||||
# How this workspace would be referenced with absolute labels from another workspace
|
||||
name = "cert_manager",
|
||||
)
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
@ -6,8 +11,11 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
|
||||
## Load rules_go and dependencies
|
||||
http_archive(
|
||||
name = "io_bazel_rules_go",
|
||||
url = "https://github.com/bazelbuild/rules_go/releases/download/0.18.2/rules_go-0.18.2.tar.gz",
|
||||
sha256 = "31f959ecf3687f6e0bb9d01e1e7a7153367ecd82816c9c0ae149cd0e5a92bf8c",
|
||||
urls = [
|
||||
"https://storage.googleapis.com/bazel-mirror/github.com/bazelbuild/rules_go/releases/download/0.18.6/rules_go-0.18.6.tar.gz",
|
||||
"https://github.com/bazelbuild/rules_go/releases/download/0.18.6/rules_go-0.18.6.tar.gz",
|
||||
],
|
||||
sha256 = "f04d2373bcaf8aa09bccb08a98a57e721306c8f6043a2a0ee610fd6853dcde3d",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
@ -36,16 +44,18 @@ gazelle_dependencies()
|
||||
## Load kubernetes repo-infra for tools like kazel
|
||||
git_repository(
|
||||
name = "io_kubernetes_build",
|
||||
commit = "df02ded38f9506e5bbcbf21702034b4fef815f2f",
|
||||
remote = "https://github.com/kubernetes/repo-infra.git",
|
||||
commit = "8036538b4efe3443d25e6325cabdc367a1926f7d",
|
||||
# Use justinsb's fork until https://github.com/kubernetes/repo-infra/pull/114 merges
|
||||
remote = "https://github.com/justinsb/repo-infra.git",
|
||||
shallow_since = "1561083176 -0400",
|
||||
)
|
||||
|
||||
## Load rules_docker and depdencies, for working with docker images
|
||||
git_repository(
|
||||
name = "io_bazel_rules_docker",
|
||||
remote = "https://github.com/bazelbuild/rules_docker.git",
|
||||
commit = "3732c9d05315bef6a3dbd195c545d6fea3b86880",
|
||||
shallow_since = "1547471117 +0100",
|
||||
commit = "80ea3aae060077e5fe0cdef1a5c570d4b7622100",
|
||||
shallow_since = "1561646721 -0700",
|
||||
)
|
||||
|
||||
load(
|
||||
@ -114,6 +124,7 @@ container_pull(
|
||||
registry = "gcr.io",
|
||||
repository = "kubernetes-helm/tiller",
|
||||
tag = "v2.10.0",
|
||||
digest = "sha256:2a3dd484ecfcf9343994e0f6c2af0a6faf1af7f7e499905793643f91e90edcb3",
|
||||
)
|
||||
|
||||
## Install 'kind', for creating kubernetes-in-docker clusters
|
||||
@ -143,6 +154,9 @@ container_pull(
|
||||
registry = "quay.io",
|
||||
repository = "kubernetes-ingress-controller/nginx-ingress-controller",
|
||||
tag = "0.23.0",
|
||||
# For some reason, the suggested sha256 returns an error when fetched from
|
||||
# quay.io by digest.
|
||||
# digest = "sha256:f7f08fdbbeddaf3179829c662da360a3feac1ecf8c4b1305949fffd8c8f59879",
|
||||
)
|
||||
|
||||
container_pull(
|
||||
@ -150,6 +164,7 @@ container_pull(
|
||||
registry = "k8s.gcr.io",
|
||||
repository = "defaultbackend",
|
||||
tag = "1.4",
|
||||
digest = "sha256:865b0c35e6da393b8e80b7e3799f777572399a4cff047eb02a81fa6e7a48ed4b",
|
||||
)
|
||||
|
||||
## Fetch vault for use during e2e tests
|
||||
@ -160,6 +175,7 @@ container_pull(
|
||||
registry = "index.docker.io",
|
||||
repository = "library/vault",
|
||||
tag = "0.9.3",
|
||||
digest = "sha256:27a564c725f4f6fa72a618add6b0c3294431ed6b5e912ee042822b35b91064c3",
|
||||
)
|
||||
|
||||
## Fetch kind images used during e2e tests
|
||||
@ -182,6 +198,7 @@ container_pull(
|
||||
registry = "index.docker.io",
|
||||
repository = "kindest/node",
|
||||
tag = "v1.13.4",
|
||||
digest = "sha256:842ffccc3ba7674f71815d40fdfd18bc8a98d18130dcfd58bc15c857593f1e15",
|
||||
)
|
||||
|
||||
## Fetch kubectl for use during e2e tests
|
||||
@ -322,7 +339,10 @@ new_git_repository(
|
||||
# We use this specific revision as it contains changes that allow us to
|
||||
# specify custom paths when building documentation.
|
||||
commit = "28714834053271ebb5a6a5fe22af29f98fc0b6d0",
|
||||
shallow_since = "1556994488 +0100",
|
||||
build_file_content = """
|
||||
exports_files(["brodoc.js"])
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = glob(["**/*"]),
|
||||
@ -342,28 +362,32 @@ filegroup(
|
||||
""",
|
||||
)
|
||||
|
||||
# Setup npm for brodocs doc generation
|
||||
git_repository(
|
||||
# Install the nodejs "bootstrap" package
|
||||
# This provides the basic tools for running and packaging nodejs programs in Bazel
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
remote = "https://github.com/bazelbuild/rules_nodejs.git",
|
||||
commit = "11271418a6bbd2529170270a7e61dcc5167bb16d",
|
||||
shallow_since = "1554849870 -0700",
|
||||
sha256 = "395b7568f20822c13fc5abc65b1eced637446389181fda3a108fdd6ff2cac1e9",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.29.2/rules_nodejs-0.29.2.tar.gz"],
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
|
||||
|
||||
# TODO: do we need to specify this package.json in node_repositories as well as
|
||||
# in npm_install?
|
||||
node_repositories(package_json = ["@brodocs//:package.json"])
|
||||
|
||||
# The npm_install rule runs yarn anytime the package.json or package-lock.json file changes.
|
||||
# It also extracts any Bazel rules distributed in an npm package.
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "npm_install")
|
||||
|
||||
npm_install(
|
||||
# Name this npm so that Bazel Label references look like @brodocs_modules//package
|
||||
name = "brodocs_modules",
|
||||
package_json = "@brodocs//:package.json",
|
||||
package_lock_json = "//docs/generated/reference/generate/bin:package-lock.json",
|
||||
)
|
||||
|
||||
# Install any Bazel rules which were extracted earlier by the npm_install rule.
|
||||
load("@brodocs_modules//:install_bazel_dependencies.bzl", "install_bazel_dependencies")
|
||||
|
||||
install_bazel_dependencies()
|
||||
|
||||
# Load the controller-tools repository in order to build the crd generator tool
|
||||
go_repository(
|
||||
name = "io_kubernetes_sigs_controller-tools",
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
load("//hack:def.bzl", "multiarch_image")
|
||||
load("//hack:def.bzl", "image")
|
||||
|
||||
# Expands to target names such as 'image.linux-amd64', 'image.linux-arm64'
|
||||
multiarch_image(
|
||||
image(
|
||||
name = "image",
|
||||
binary = ":acmesolver",
|
||||
component = "acmesolver",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@ -22,6 +22,7 @@ go_library(
|
||||
go_binary(
|
||||
name = "acmesolver",
|
||||
embed = [":go_default_library"],
|
||||
pure = "on",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
load("//hack:def.bzl", "multiarch_image")
|
||||
load("//hack:def.bzl", "image")
|
||||
|
||||
# Expands to target names such as 'image.linux-amd64', 'image.linux-arm64'
|
||||
multiarch_image(
|
||||
image(
|
||||
name = "image",
|
||||
binary = ":cainjector",
|
||||
component = "cainjector",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@ -32,6 +32,7 @@ go_library(
|
||||
go_binary(
|
||||
name = "cainjector",
|
||||
embed = [":go_default_library"],
|
||||
pure = "on",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
load("//hack:def.bzl", "multiarch_image")
|
||||
load("//hack:def.bzl", "image")
|
||||
|
||||
# Expands to target names such as 'image.linux-amd64', 'image.linux-arm64'
|
||||
multiarch_image(
|
||||
image(
|
||||
name = "image",
|
||||
binary = ":controller",
|
||||
component = "controller",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@ -43,6 +43,7 @@ go_library(
|
||||
go_binary(
|
||||
name = "controller",
|
||||
embed = [":go_default_library"],
|
||||
pure = "on",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
load("//hack:def.bzl", "multiarch_image")
|
||||
load("//hack:def.bzl", "image")
|
||||
|
||||
# Expands to target names such as 'image.linux-amd64', 'image.linux-arm64'
|
||||
multiarch_image(
|
||||
image(
|
||||
name = "image",
|
||||
binary = ":webhook",
|
||||
component = "webhook",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@ -23,6 +23,7 @@ go_library(
|
||||
go_binary(
|
||||
name = "webhook",
|
||||
embed = [":go_default_library"],
|
||||
pure = "on",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
||||
@ -24,6 +24,9 @@ rules:
|
||||
- apiGroups: ["apiregistration.k8s.io"]
|
||||
resources: ["apiservices"]
|
||||
verbs: ["get", "list", "watch", "update"]
|
||||
- apiGroups: ["apiextensions.k8s.io"]
|
||||
resources: ["customresourcedefinitions"]
|
||||
verbs: ["get", "list", "watch", "update"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
|
||||
@ -750,6 +750,11 @@ spec:
|
||||
properties:
|
||||
acme:
|
||||
properties:
|
||||
lastRegisteredEmail:
|
||||
description: LastRegisteredEmail is the email associated with the
|
||||
latest registered ACME account, in order to track changes made
|
||||
to registered account associated with the Issuer
|
||||
type: string
|
||||
uri:
|
||||
description: URI is the unique account identifier, which can also
|
||||
be used to retrieve account details from the CA
|
||||
@ -1029,6 +1034,11 @@ spec:
|
||||
properties:
|
||||
acme:
|
||||
properties:
|
||||
lastRegisteredEmail:
|
||||
description: LastRegisteredEmail is the email associated with the
|
||||
latest registered ACME account, in order to track changes made
|
||||
to registered account associated with the Issuer
|
||||
type: string
|
||||
uri:
|
||||
description: URI is the unique account identifier, which can also
|
||||
be used to retrieve account details from the CA
|
||||
|
||||
@ -82,6 +82,10 @@ Deploy that version with helm
|
||||
$ helm install \
|
||||
--set image.tag=canary \
|
||||
--set image.pullPolicy=Never \
|
||||
--set cainjector.image.tag=canary \
|
||||
--set cainjector.pullPolicy=Never \
|
||||
--set webhook.image.tag=canary \
|
||||
--set webhook.pullPolicy=Never \
|
||||
--name cert-manager \
|
||||
./deploy/charts/cert-manager
|
||||
|
||||
|
||||
@ -58,11 +58,11 @@ genrule(
|
||||
srcs = [
|
||||
":__internal_brodocs_out",
|
||||
"@brodocs//:static",
|
||||
"@brodocs_modules//node_modules/jquery:jquery__files",
|
||||
"@brodocs_modules//node_modules/bootstrap:bootstrap__files",
|
||||
"@brodocs_modules//node_modules/font-awesome:font-awesome__files",
|
||||
"@brodocs_modules//node_modules/highlight.js:highlight.js__files",
|
||||
"@brodocs_modules//node_modules/jquery.scrollto:jquery.scrollto__files",
|
||||
"@brodocs_modules//jquery:jquery__contents",
|
||||
"@brodocs_modules//bootstrap:bootstrap__contents",
|
||||
"@brodocs_modules//font-awesome:font-awesome__contents",
|
||||
"@brodocs_modules//highlight.js:highlight.js__contents",
|
||||
"@brodocs_modules//jquery.scrollto:jquery.scrollto__contents",
|
||||
],
|
||||
outs = ["generated.tar.gz"],
|
||||
cmd = "; ".join([
|
||||
|
||||
@ -45,7 +45,7 @@ Certificate resources:
|
||||
to issue a self signed root CA certificate.
|
||||
* certificate/cert-manager-webhook-ca - A self-signed root CA certificate
|
||||
which is used to sign certificates for the webhook pod.
|
||||
* issue/cert-manager-webhook-ca - A CA Issuer that is used to issue
|
||||
* issuer/cert-manager-webhook-ca - A CA Issuer that is used to issue
|
||||
certificates used by the webhook pod to serve with.
|
||||
* certificate/cert-manager-webhook-webhook-tls - A TLS certificate issued by the
|
||||
root CA above, served by the webhook.
|
||||
|
||||
@ -76,7 +76,7 @@ The Certificate will be issued using the issuer named ``ca-issuer`` in the
|
||||
issues, leading to the working duration of a certificate to be less than
|
||||
the full duration of the certificate. For example, Let's Encrypt sets it
|
||||
to be one hour before issue time, so the actual *working duration* of the
|
||||
certificate is 89 days, 23 hours (the *full duration* remains 90 days).
|
||||
certificate is 89 days, 23 hours (the *full duration* remains 90 days).
|
||||
|
||||
A full list of the fields supported on the Certificate resource can be found in
|
||||
the `API reference documentation`_.
|
||||
@ -101,6 +101,9 @@ After the real, valid certificate has been obtained, cert-manager will replace
|
||||
the temporary self signed certificate with the valid one, **but will retain the
|
||||
same private key**.
|
||||
|
||||
You can disable issuing temporary certificate by setting feature gate flag
|
||||
``--feature-gates=IssueTemporaryCertificate=false``
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
104
hack/def.bzl
104
hack/def.bzl
@ -16,98 +16,30 @@ load("@io_bazel_rules_docker//container:image.bzl", "container_image")
|
||||
load("@io_bazel_rules_docker//container:bundle.bzl", "container_bundle")
|
||||
load("@io_bazel_rules_docker//go:image.bzl", "go_image")
|
||||
|
||||
## stamped_image is a macro for creating :app and :image targets
|
||||
def stamped_image(
|
||||
name, # use "image"
|
||||
base = None,
|
||||
user = "1000",
|
||||
stamp = True, # stamp by default, but allow overrides
|
||||
**kwargs):
|
||||
go_image(
|
||||
name = "%s.app" % name,
|
||||
base = base,
|
||||
embed = [":go_default_library"],
|
||||
goarch = "amd64",
|
||||
goos = "linux",
|
||||
pure = "on",
|
||||
)
|
||||
|
||||
container_image(
|
||||
name = name,
|
||||
base = ":%s.app" % name,
|
||||
user = user,
|
||||
stamp = stamp,
|
||||
**kwargs)
|
||||
|
||||
def multiarch_image(
|
||||
def image(
|
||||
name,
|
||||
component,
|
||||
goarch = ["amd64", "arm64", "arm"],
|
||||
goos = ["linux"],
|
||||
binary,
|
||||
user = "1000",
|
||||
stamp = True,
|
||||
**kwargs):
|
||||
|
||||
for arch in goarch:
|
||||
for os in goos:
|
||||
go_image(
|
||||
name = "%s.app_%s-%s" % (name, os, arch),
|
||||
base = "@static_base//image",
|
||||
embed = [":go_default_library"],
|
||||
goarch = arch,
|
||||
goos = os,
|
||||
pure = "on",
|
||||
)
|
||||
go_image(
|
||||
name = "%s.app" % name,
|
||||
base = "@static_base//image",
|
||||
binary = binary,
|
||||
)
|
||||
|
||||
container_image(
|
||||
name = "%s.%s-%s" % (name, os, arch),
|
||||
base = "%s.app_%s-%s" % (name, os, arch),
|
||||
user = user,
|
||||
stamp = stamp,
|
||||
**kwargs)
|
||||
|
||||
suffix = ""
|
||||
if arch != "amd64":
|
||||
suffix = "-%s" % arch
|
||||
|
||||
container_bundle(
|
||||
name = "%s.%s-%s.export" % (name, os, arch),
|
||||
images = {
|
||||
("{STABLE_DOCKER_REPO}/cert-manager-%s%s:{STABLE_APP_VERSION}" % (component, suffix)): ":%s.%s-%s" % (name, os, arch),
|
||||
("{STABLE_DOCKER_REPO}/cert-manager-%s%s:{STABLE_APP_GIT_COMMIT}" % (component, suffix)): ":%s.%s-%s" % (name, os, arch),
|
||||
},
|
||||
)
|
||||
|
||||
container_image(
|
||||
name = name,
|
||||
base = "%s.%s-%s" % (name, goos[0], goarch[0]),
|
||||
**kwargs)
|
||||
|
||||
def multiarch_bundle(
|
||||
name,
|
||||
images,
|
||||
os = ["linux"],
|
||||
arch = ["amd64", "arm64", "arm"],
|
||||
**kwargs):
|
||||
|
||||
all_images = {}
|
||||
for a in arch:
|
||||
for o in os:
|
||||
oa_images = {}
|
||||
for (k, v) in images.items():
|
||||
image_name = k.replace("{arch}", a)
|
||||
image_name = image_name.replace("{os}", o)
|
||||
|
||||
oa_images[image_name] = "%s.%s-%s" % (v, o, a)
|
||||
|
||||
container_bundle(
|
||||
name = "%s.%s-%s" % (name, o, a),
|
||||
images = oa_images,
|
||||
**kwargs)
|
||||
|
||||
all_images.update(oa_images)
|
||||
container_image(
|
||||
name = name,
|
||||
base = "%s.app" % name,
|
||||
user = user,
|
||||
stamp = stamp,
|
||||
**kwargs)
|
||||
|
||||
container_bundle(
|
||||
name = name,
|
||||
images = all_images,
|
||||
**kwargs)
|
||||
name = name + ".export",
|
||||
images = {
|
||||
component + ":{STABLE_APP_GIT_COMMIT}": ":" + name,
|
||||
},
|
||||
)
|
||||
|
||||
@ -68,7 +68,7 @@ func (g *Bazel) Cmd(ctx context.Context, args ...string) *exec.Cmd {
|
||||
fmt.Sprintf("APP_VERSION=%s", flags.Default.AppVersion),
|
||||
fmt.Sprintf("APP_GIT_COMMIT=%s", flags.Default.GitCommitRef),
|
||||
)
|
||||
log.Info("set command environment variables", "env", cmd.Env)
|
||||
log.V(logf.LogLevelTrace).Info("set command environment variables", "env", cmd.Env)
|
||||
cmd.Dir = flags.Default.RepoRoot
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -34,7 +34,6 @@ import (
|
||||
var (
|
||||
Default = &Plugin{}
|
||||
|
||||
supportedGoArch = []string{"amd64", "arm64", "arm"}
|
||||
supportedComponents = []string{"acmesolver", "controller", "webhook", "cainjector"}
|
||||
log = logf.Log.WithName("images")
|
||||
)
|
||||
@ -72,20 +71,6 @@ func (g *Plugin) AddFlags(fs *flag.FlagSet) {
|
||||
func (g *Plugin) Validate() []error {
|
||||
var errs []error
|
||||
|
||||
// validate goarch flag
|
||||
for _, a := range g.GoArch {
|
||||
valid := false
|
||||
for _, sa := range supportedGoArch {
|
||||
if a == sa {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
errs = append(errs, fmt.Errorf("invalid goarch value %q", a))
|
||||
}
|
||||
}
|
||||
|
||||
// validate components flag
|
||||
for _, a := range g.Components {
|
||||
valid := false
|
||||
@ -175,19 +160,18 @@ func (g *Plugin) Complete() error {
|
||||
func (g *Plugin) build(ctx context.Context) (imageTargets, error) {
|
||||
targets := g.generateTargets()
|
||||
|
||||
bazelTargets := targets.bazelTargets()
|
||||
log := log.WithValues("images", bazelTargets)
|
||||
log.Info("building bazel image targets")
|
||||
// only support building docker images for linux for now
|
||||
os := "linux"
|
||||
for _, arch := range g.GoArch {
|
||||
filteredTargets := targets.withOSArch(os, arch)
|
||||
bazelTargets := filteredTargets.bazelTargets()
|
||||
log := log.WithValues("images", bazelTargets)
|
||||
log.Info("building bazel image targets")
|
||||
|
||||
// set the os and arch to linux/amd64
|
||||
// whilst we might be building cross-arch binaries, cgo only depends on
|
||||
// particular OS settings and not arch, so just by setting this to 'linux'
|
||||
// we can fix cross builds on platforms other than linux
|
||||
// if we support alternate OS values in future, this will need updating
|
||||
// with a call to BuildPlatformE per *OS*.
|
||||
err := bazel.Default.BuildPlatformE(ctx, log, "linux", "amd64", bazelTargets...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building docker images (%v): %v", targets, err)
|
||||
err := bazel.Default.BuildPlatformE(ctx, log, os, arch, bazelTargets...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building docker images (%v): %v", targets, err)
|
||||
}
|
||||
}
|
||||
|
||||
g.built = true
|
||||
@ -197,22 +181,26 @@ func (g *Plugin) build(ctx context.Context) (imageTargets, error) {
|
||||
func (g *Plugin) exportToDocker(ctx context.Context) error {
|
||||
targets := g.generateTargets()
|
||||
log.WithValues("images", targets.bazelExportTargets()).Info("exporting images to docker daemon")
|
||||
for _, target := range targets.bazelExportTargets() {
|
||||
log := log.WithValues("target", target)
|
||||
for _, target := range targets {
|
||||
log := log.WithValues("target", target.name, "os", target.os, "arch", target.arch)
|
||||
log.Info("exporting image to docker daemon")
|
||||
// set the os and arch to linux/amd64
|
||||
// whilst we might be building cross-arch binaries, cgo only depends on
|
||||
// particular OS settings and not arch, so just by setting this to 'linux'
|
||||
// we can fix cross builds on platforms other than linux
|
||||
// if we support alternate OS values in future, this will need updating
|
||||
// with a call to BuildPlatformE per *OS*.
|
||||
err := bazel.Default.RunPlatformE(ctx, log, "linux", "amd64", target)
|
||||
exportTarget := target.bazelExportTarget()
|
||||
err := bazel.Default.RunPlatformE(ctx, log, target.os, target.arch, exportTarget)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error exporting image %q to docker daemon: %v", target, err)
|
||||
}
|
||||
|
||||
for _, taggedImage := range target.taggedImageNames() {
|
||||
log.Info("tagging image", "tag", taggedImage)
|
||||
cmd := exec.CommandContext(ctx, "docker", "tag", target.exportedImageName(), taggedImage)
|
||||
err := util.RunE(log, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.WithValues("images", targets.exportedImageNames()).Info("exported all docker images")
|
||||
log.WithValues("images", targets.taggedImageNames()).Info("exported all docker images")
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -237,10 +225,7 @@ func (p *Plugin) pushImages(ctx context.Context, targets imageTargets) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var images []string
|
||||
for _, t := range targets {
|
||||
images = append(images, t.exportedImageNames()...)
|
||||
}
|
||||
images := targets.taggedImageNames()
|
||||
|
||||
log.WithValues("images", images).Info("pushing docker images")
|
||||
for _, img := range images {
|
||||
@ -279,10 +264,28 @@ func (i imageTargets) bazelExportTargets() []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func (i imageTargets) taggedImageNames() []string {
|
||||
out := make([]string, 0)
|
||||
for _, target := range i {
|
||||
out = append(out, target.taggedImageNames()...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (i imageTargets) exportedImageNames() []string {
|
||||
out := make([]string, 0)
|
||||
for _, target := range i {
|
||||
out = append(out, target.exportedImageNames()...)
|
||||
out = append(out, target.taggedImageNames()...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (i imageTargets) withOSArch(os, arch string) imageTargets {
|
||||
out := make(imageTargets, 0)
|
||||
for _, target := range i {
|
||||
if target.os == os && target.arch == arch {
|
||||
out = append(out, target)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@ -292,14 +295,18 @@ type imageTarget struct {
|
||||
}
|
||||
|
||||
func (i imageTarget) bazelTarget() string {
|
||||
return fmt.Sprintf("//cmd/%s:image.%s-%s", i.name, i.os, i.arch)
|
||||
return fmt.Sprintf("//cmd/%s:image", i.name)
|
||||
}
|
||||
|
||||
func (i imageTarget) bazelExportTarget() string {
|
||||
return fmt.Sprintf("//cmd/%s:image.%s-%s.export", i.name, i.os, i.arch)
|
||||
return fmt.Sprintf("//cmd/%s:image.export", i.name)
|
||||
}
|
||||
|
||||
func (i imageTarget) exportedImageNames() []string {
|
||||
func (i imageTarget) exportedImageName() string {
|
||||
return fmt.Sprintf("%s:%s", i.name, flags.Default.GitCommitRef)
|
||||
}
|
||||
|
||||
func (i imageTarget) taggedImageNames() []string {
|
||||
if i.arch == "amd64" {
|
||||
return []string{
|
||||
fmt.Sprintf("%s/cert-manager-%s:%s", flags.Default.DockerRepo, i.name, flags.Default.AppVersion),
|
||||
|
||||
@ -42,6 +42,7 @@ type FakeACME struct {
|
||||
FakeHTTP01ChallengeResponse func(token string) (string, error)
|
||||
FakeDNS01ChallengeRecord func(token string) (string, error)
|
||||
FakeDiscover func(ctx context.Context) (acme.Directory, error)
|
||||
FakeUpdateAccount func(ctx context.Context, a *acme.Account) (*acme.Account, error)
|
||||
}
|
||||
|
||||
func (f *FakeACME) CreateOrder(ctx context.Context, order *acme.Order) (*acme.Order, error) {
|
||||
@ -143,3 +144,10 @@ func (f *FakeACME) Discover(ctx context.Context) (acme.Directory, error) {
|
||||
// empty directory here will be fine
|
||||
return acme.Directory{}, nil
|
||||
}
|
||||
|
||||
func (f *FakeACME) UpdateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) {
|
||||
if f.FakeUpdateAccount != nil {
|
||||
return f.FakeUpdateAccount(ctx, a)
|
||||
}
|
||||
return nil, fmt.Errorf("UpdateAccount not implemented")
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ type Interface interface {
|
||||
HTTP01ChallengeResponse(token string) (string, error)
|
||||
DNS01ChallengeRecord(token string) (string, error)
|
||||
Discover(ctx context.Context) (acme.Directory, error)
|
||||
UpdateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error)
|
||||
}
|
||||
|
||||
var _ Interface = &acme.Client{}
|
||||
|
||||
@ -103,3 +103,8 @@ func (l *Logger) Discover(ctx context.Context) (acme.Directory, error) {
|
||||
klog.Infof("Calling Discover")
|
||||
return l.baseCl.Discover(ctx)
|
||||
}
|
||||
|
||||
func (l *Logger) UpdateAccount(ctx context.Context, a *acme.Account) (*acme.Account, error) {
|
||||
klog.Infof("Calling UpdateAccount")
|
||||
return l.baseCl.UpdateAccount(ctx, a)
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ go_library(
|
||||
deps = [
|
||||
"//pkg/acme/webhook/apis/acme/v1alpha1:go_default_library",
|
||||
"//pkg/apis/certmanager/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
|
||||
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package api
|
||||
|
||||
import (
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@ -37,6 +38,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
whapi.AddToScheme,
|
||||
kscheme.AddToScheme,
|
||||
apireg.AddToScheme,
|
||||
apiext.AddToScheme,
|
||||
}
|
||||
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
|
||||
@ -542,6 +542,12 @@ type ACMEIssuerStatus struct {
|
||||
// account details from the CA
|
||||
// +optional
|
||||
URI string `json:"uri,omitempty"`
|
||||
|
||||
// LastRegisteredEmail is the email associated with the latest registered
|
||||
// ACME account, in order to track changes made to registered account
|
||||
// associated with the Issuer
|
||||
// +optional
|
||||
LastRegisteredEmail string `json:"lastRegisteredEmail,omitempty"`
|
||||
}
|
||||
|
||||
// IssuerCondition contains condition information for an Issuer.
|
||||
|
||||
@ -17,6 +17,7 @@ go_library(
|
||||
"//vendor/github.com/go-logr/logr:go_default_library",
|
||||
"//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
|
||||
@ -144,6 +144,10 @@ func (r *genericInjectReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
|
||||
// fetch the target object
|
||||
target := r.injector.NewTarget()
|
||||
if err := r.Client.Get(ctx, req.NamespacedName, target.AsObject()); err != nil {
|
||||
if dropNotFound(err) == nil {
|
||||
// don't requeue on deletions, which yield a non-found object
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
log.Error(err, "unable to fetch target object to inject into")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package cainjector
|
||||
|
||||
import (
|
||||
admissionreg "k8s.io/api/admissionregistration/v1beta1"
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
||||
)
|
||||
@ -93,3 +94,27 @@ func (t *apiServiceTarget) SetCA(data []byte) {
|
||||
}
|
||||
|
||||
// TODO(directxman12): conversion webhooks
|
||||
// crdConversionInjector knows how to create an InjectTarget for CRD conversion webhooks
|
||||
type crdConversionInjector struct{}
|
||||
|
||||
func (i crdConversionInjector) NewTarget() InjectTarget {
|
||||
return &crdConversionTarget{}
|
||||
}
|
||||
|
||||
// crdConversionTarget knows how to set CA data for the conversion webhook in CRDs
|
||||
type crdConversionTarget struct {
|
||||
obj apiext.CustomResourceDefinition
|
||||
}
|
||||
|
||||
func (t *crdConversionTarget) AsObject() runtime.Object {
|
||||
return &t.obj
|
||||
}
|
||||
func (t *crdConversionTarget) SetCA(data []byte) {
|
||||
if t.obj.Spec.Conversion == nil || t.obj.Spec.Conversion.Strategy != apiext.WebhookConverter {
|
||||
return
|
||||
}
|
||||
if t.obj.Spec.Conversion.WebhookClientConfig == nil {
|
||||
t.obj.Spec.Conversion.WebhookClientConfig = &apiext.WebhookClientConfig{}
|
||||
}
|
||||
t.obj.Spec.Conversion.WebhookClientConfig.CABundle = data
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
|
||||
admissionreg "k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@ -56,7 +57,13 @@ var (
|
||||
listType: &apireg.APIServiceList{},
|
||||
}
|
||||
|
||||
injectorSetups = []injectorSetup{MutatingWebhookSetup, ValidatingWebhookSetup, APIServiceSetup}
|
||||
CRDSetup = injectorSetup{
|
||||
resourceName: "customresourcedefinition",
|
||||
injector: crdConversionInjector{},
|
||||
listType: &apiext.CustomResourceDefinitionList{},
|
||||
}
|
||||
|
||||
injectorSetups = []injectorSetup{MutatingWebhookSetup, ValidatingWebhookSetup, APIServiceSetup, CRDSetup}
|
||||
ControllerNames []string
|
||||
)
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ go_library(
|
||||
"//pkg/client/clientset/versioned:go_default_library",
|
||||
"//pkg/client/listers/certmanager/v1alpha1:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/feature:go_default_library",
|
||||
"//pkg/issuer:go_default_library",
|
||||
"//pkg/logs:go_default_library",
|
||||
"//pkg/metrics:go_default_library",
|
||||
@ -31,6 +32,7 @@ go_library(
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
|
||||
@ -33,11 +33,13 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
apiutil "github.com/jetstack/cert-manager/pkg/api/util"
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/validation"
|
||||
"github.com/jetstack/cert-manager/pkg/feature"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer"
|
||||
logf "github.com/jetstack/cert-manager/pkg/logs"
|
||||
"github.com/jetstack/cert-manager/pkg/util"
|
||||
@ -404,6 +406,8 @@ func (c *controller) updateSecret(ctx context.Context, crt *v1alpha1.Certificate
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid certificate data: %v", err)
|
||||
}
|
||||
case !utilfeature.DefaultFeatureGate.Enabled(feature.IssueTemporaryCertificate):
|
||||
break
|
||||
case isTemporaryCertificate(existingCert):
|
||||
matches, err := pki.PublicKeyMatchesCertificate(privKey.Public(), existingCert)
|
||||
if err == nil && matches {
|
||||
@ -435,11 +439,13 @@ func (c *controller) updateSecret(ctx context.Context, crt *v1alpha1.Certificate
|
||||
// TODO: move metadata setting out of this method, and support
|
||||
// retrospectively adding metadata annotations on every Sync iteration and
|
||||
// not just when a new certificate is issued
|
||||
secret.Annotations[v1alpha1.IssuerNameAnnotationKey] = crt.Spec.IssuerRef.Name
|
||||
secret.Annotations[v1alpha1.IssuerKindAnnotationKey] = issuerKind(crt)
|
||||
secret.Annotations[v1alpha1.CommonNameAnnotationKey] = x509Cert.Subject.CommonName
|
||||
secret.Annotations[v1alpha1.AltNamesAnnotationKey] = strings.Join(x509Cert.DNSNames, ",")
|
||||
secret.Annotations[v1alpha1.IPSANAnnotationKey] = strings.Join(pki.IPAddressesToString(x509Cert.IPAddresses), ",")
|
||||
if x509Cert != nil {
|
||||
secret.Annotations[v1alpha1.IssuerNameAnnotationKey] = crt.Spec.IssuerRef.Name
|
||||
secret.Annotations[v1alpha1.IssuerKindAnnotationKey] = issuerKind(crt)
|
||||
secret.Annotations[v1alpha1.CommonNameAnnotationKey] = x509Cert.Subject.CommonName
|
||||
secret.Annotations[v1alpha1.AltNamesAnnotationKey] = strings.Join(x509Cert.DNSNames, ",")
|
||||
secret.Annotations[v1alpha1.IPSANAnnotationKey] = strings.Join(pki.IPAddressesToString(x509Cert.IPAddresses), ",")
|
||||
}
|
||||
|
||||
// Always set the certificate name label on the target secret
|
||||
secret.Labels[v1alpha1.CertificateNameKey] = crt.Name
|
||||
|
||||
@ -26,6 +26,11 @@ const (
|
||||
//
|
||||
// ValidateCAA enables CAA checking when issuing certificates
|
||||
ValidateCAA feature.Feature = "ValidateCAA"
|
||||
|
||||
// beta: v0.8.1
|
||||
//
|
||||
// IssueTemporaryCertificate enables issuing temporary certificates
|
||||
IssueTemporaryCertificate feature.Feature = "IssueTemporaryCertificate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -36,5 +41,6 @@ func init() {
|
||||
// To add a new feature, define a key for it above and add it here. The features will be
|
||||
// available throughout Kubernetes binaries.
|
||||
var defaultKubernetesFeatureGates = map[feature.Feature]feature.FeatureSpec{
|
||||
ValidateCAA: {Default: false, PreRelease: feature.Alpha},
|
||||
ValidateCAA: {Default: false, PreRelease: feature.Alpha},
|
||||
IssueTemporaryCertificate: {Default: true, PreRelease: feature.Beta},
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ func (h *HTTP01Solver) Listen(ctx context.Context) error {
|
||||
)
|
||||
if r.URL.EscapedPath() == "/" || r.URL.EscapedPath() == "/healthz" {
|
||||
log.Info("responding OK to health check")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
@ -84,6 +85,7 @@ func (h *HTTP01Solver) Listen(ctx context.Context) error {
|
||||
}
|
||||
|
||||
log.Info("got successful challenge request, writing key")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, h.Key)
|
||||
})
|
||||
|
||||
@ -23,7 +23,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
@ -40,12 +40,14 @@ import (
|
||||
const (
|
||||
errorAccountRegistrationFailed = "ErrRegisterACMEAccount"
|
||||
errorAccountVerificationFailed = "ErrVerifyACMEAccount"
|
||||
errorAccountUpdateFailed = "ErrUpdateACMEAccount"
|
||||
|
||||
successAccountRegistered = "ACMEAccountRegistered"
|
||||
successAccountVerified = "ACMEAccountVerified"
|
||||
|
||||
messageAccountRegistrationFailed = "Failed to register ACME account: "
|
||||
messageAccountVerificationFailed = "Failed to verify ACME account: "
|
||||
messageAccountUpdateFailed = "Failed to update ACME account:"
|
||||
messageAccountRegistered = "The ACME account was registered with the ACME server"
|
||||
messageAccountVerified = "The ACME account was verified with the ACME server"
|
||||
)
|
||||
@ -147,12 +149,14 @@ func (a *Acme) Setup(ctx context.Context) error {
|
||||
Status: v1alpha1.ConditionTrue,
|
||||
})
|
||||
|
||||
// If the Host components of the server URL and the account URL match, then
|
||||
// If the Host components of the server URL and the account URL match,
|
||||
// and the cached email matches the registered email, then
|
||||
// we skip re-checking the account status to save excess calls to the
|
||||
// ACME api.
|
||||
if hasReadyCondition &&
|
||||
a.issuer.GetStatus().ACMEStatus().URI != "" &&
|
||||
parsedAccountURL.Host == parsedServerURL.Host {
|
||||
parsedAccountURL.Host == parsedServerURL.Host &&
|
||||
a.issuer.GetStatus().ACMEStatus().LastRegisteredEmail == a.issuer.GetSpec().ACME.Email {
|
||||
log.Info("skipping re-verifying ACME account as cached registration " +
|
||||
"details look sufficient")
|
||||
return nil
|
||||
@ -192,9 +196,57 @@ func (a *Acme) Setup(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we got an account successfully, we must check if the registered
|
||||
// email is the same as in the issuer spec
|
||||
|
||||
// if no email was specified, then registeredEmail will remain empty
|
||||
registeredEmail := ""
|
||||
if len(account.Contact) > 0 {
|
||||
registeredEmail = strings.Replace(account.Contact[0], "mailto:", "", 1)
|
||||
}
|
||||
|
||||
// if they are different, we update the account
|
||||
specEmail := a.issuer.GetSpec().ACME.Email
|
||||
if registeredEmail != specEmail {
|
||||
log.Info("Updating ACME account with email %s", specEmail)
|
||||
emailurl := []string(nil)
|
||||
if a.issuer.GetSpec().ACME.Email != "" {
|
||||
emailurl = []string{fmt.Sprintf("mailto:%s", strings.ToLower(specEmail))}
|
||||
}
|
||||
account.Contact = emailurl
|
||||
|
||||
account, err = cl.UpdateAccount(ctx, account)
|
||||
if err != nil {
|
||||
s := messageAccountUpdateFailed + err.Error()
|
||||
log.Error(err, "failed to update ACME account")
|
||||
a.Recorder.Event(a.issuer, v1.EventTypeWarning, errorAccountUpdateFailed, s)
|
||||
apiutil.SetIssuerCondition(a.issuer, v1alpha1.IssuerConditionReady, v1alpha1.ConditionFalse, errorAccountUpdateFailed, s)
|
||||
|
||||
acmeErr, ok := err.(*acmeapi.Error)
|
||||
// If this is not an ACME error, we will simply return it and retry later
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the status code is 400 (BadRequest), we will *not* retry this registration
|
||||
// as it implies that something about the request (i.e. email address or private key)
|
||||
// is invalid.
|
||||
if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 {
|
||||
log.Error(acmeErr, "skipping updating account email as a "+
|
||||
"BadRequest response was returned from the ACME server")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise if we receive anything other than a 400, we will retry.
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Info("verified existing registration with ACME server")
|
||||
apiutil.SetIssuerCondition(a.issuer, v1alpha1.IssuerConditionReady, v1alpha1.ConditionTrue, successAccountRegistered, messageAccountRegistered)
|
||||
a.issuer.GetStatus().ACMEStatus().URI = account.URL
|
||||
a.issuer.GetStatus().ACMEStatus().LastRegisteredEmail = registeredEmail
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"issue_test.go",
|
||||
"sign_test.go",
|
||||
"util_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
|
||||
@ -90,26 +90,14 @@ func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) (*issuer.Issu
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caCert := caCerts[0]
|
||||
template.PublicKey = signeePublicKey
|
||||
|
||||
// sign and encode the certificate
|
||||
certPem, _, err := pki.SignCertificate(template, caCert, signeePublicKey, caKey)
|
||||
resp, err := pki.SignCSRTemplate(caCerts, caKey, template)
|
||||
if err != nil {
|
||||
log.Error(err, "error signing certificate")
|
||||
c.Recorder.Eventf(crt, corev1.EventTypeWarning, "ErrorSigning", "Error signing certificate: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// encode the chain
|
||||
// TODO: replace caCerts with caCerts[1:]?
|
||||
chainPem, err := pki.EncodeX509Chain(caCerts)
|
||||
if err != nil {
|
||||
log.Error(err, "error encoding x509 certificate chain")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPem = append(certPem, chainPem...)
|
||||
|
||||
// Encode output private key and CA cert ready for return
|
||||
keyPem, err := pki.EncodePrivateKey(signeeKey, crt.Spec.KeyEncoding)
|
||||
if err != nil {
|
||||
@ -117,20 +105,9 @@ func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) (*issuer.Issu
|
||||
c.Recorder.Eventf(crt, corev1.EventTypeWarning, "ErrorPrivateKey", "Error encoding private key: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// encode the CA certificate to be bundled in the output
|
||||
caPem, err := pki.EncodeX509(caCerts[0])
|
||||
if err != nil {
|
||||
log.Error(err, "error encoding certificate")
|
||||
c.Recorder.Eventf(crt, corev1.EventTypeWarning, "ErrorSigning", "Error encoding certificate: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
resp.PrivateKey = keyPem
|
||||
|
||||
log.Info("certificate issued")
|
||||
|
||||
return &issuer.IssueResponse{
|
||||
PrivateKey: keyPem,
|
||||
Certificate: certPem,
|
||||
CA: caPem,
|
||||
}, nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@ -86,6 +86,33 @@ func allFieldsSetCheck(expectedCA []byte) func(t *testing.T, s *caFixture, args
|
||||
if resp.PrivateKey == nil {
|
||||
t.Errorf("expected new private key to be generated")
|
||||
}
|
||||
|
||||
certificatesFieldsSetCheck(expectedCA)(t, s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func noPrivateKeyFieldsSetCheck(expectedCA []byte) func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
return func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
resp := args[1].(*issuer.IssueResponse)
|
||||
|
||||
if resp == nil {
|
||||
t.Errorf("no response given, got=%s", resp)
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.PrivateKey) > 0 {
|
||||
t.Errorf("expected no new private key to be generated but got: %s",
|
||||
resp.PrivateKey)
|
||||
}
|
||||
|
||||
certificatesFieldsSetCheck(expectedCA)(t, s, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func certificatesFieldsSetCheck(expectedCA []byte) func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
return func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
resp := args[1].(*issuer.IssueResponse)
|
||||
|
||||
if resp.Certificate == nil {
|
||||
t.Errorf("expected new certificate to be issued")
|
||||
}
|
||||
|
||||
@ -18,12 +18,46 @@ package ca
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer"
|
||||
logf "github.com/jetstack/cert-manager/pkg/logs"
|
||||
"github.com/jetstack/cert-manager/pkg/util/kube"
|
||||
"github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
)
|
||||
|
||||
func (c *CA) Sign(ctx context.Context, cr *v1alpha1.CertificateRequest) (*issuer.IssueResponse, error) {
|
||||
return nil, errors.New("sign not implemented by CA issuer")
|
||||
log := logf.FromContext(ctx, "sign")
|
||||
|
||||
// get a copy of the CA certificate named on the Issuer
|
||||
caCerts, caKey, err := kube.SecretTLSKeyPair(ctx, c.secretsLister, c.resourceNamespace, c.issuer.GetSpec().CA.SecretName)
|
||||
if err != nil {
|
||||
log := logf.WithRelatedResourceName(log, c.issuer.GetSpec().CA.SecretName, c.resourceNamespace, "Secret")
|
||||
log.Info("error getting signing CA for Issuer")
|
||||
|
||||
// We're fine to return errors here and later since upon a retry the issuer
|
||||
// will be marked as 'not ready' - therefore this codepath wont be reached
|
||||
// again
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
|
||||
if err != nil {
|
||||
log.Error(err, "error generating certificate template")
|
||||
c.Recorder.Eventf(cr, corev1.EventTypeWarning, "ErrorSigning", "Error generating certificate template: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := pki.SignCSRTemplate(caCerts, caKey, template)
|
||||
if err != nil {
|
||||
log.Error(err, "error signing certificate")
|
||||
c.Recorder.Eventf(cr, corev1.EventTypeWarning, "ErrorSigning", "Error signing certificate: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("certificate issued")
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
257
pkg/issuer/ca/sign_test.go
Normal file
257
pkg/issuer/ca/sign_test.go
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
Copyright 2019 The Jetstack cert-manager contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ca
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer"
|
||||
"github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
"github.com/jetstack/cert-manager/test/unit/gen"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func generateCSR(t *testing.T, secretKey crypto.Signer) ([]byte, error) {
|
||||
asn1Subj, _ := asn1.Marshal(pkix.Name{
|
||||
CommonName: "test",
|
||||
}.ToRDNSequence())
|
||||
template := x509.CertificateRequest{
|
||||
RawSubject: asn1Subj,
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
}
|
||||
|
||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
|
||||
|
||||
return csr, nil
|
||||
}
|
||||
|
||||
func generateSelfSignedCertFromCR(t *testing.T, cr *v1alpha1.CertificateRequest, key crypto.Signer,
|
||||
duration time.Duration) (derBytes, pemBytes []byte) {
|
||||
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
|
||||
if err != nil {
|
||||
t.Errorf("error generating template: %v", err)
|
||||
}
|
||||
|
||||
derBytes, err = x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if err != nil {
|
||||
t.Errorf("error signing cert: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
pemByteBuffer := bytes.NewBuffer([]byte{})
|
||||
err = pem.Encode(pemByteBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
if err != nil {
|
||||
t.Errorf("failed to encode cert: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return derBytes, pemByteBuffer.Bytes()
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
// Build root RSA CA
|
||||
rsaPK := generateRSAPrivateKey(t)
|
||||
rsaPKBytes := pki.EncodePKCS1PrivateKey(rsaPK)
|
||||
|
||||
caCSR, err := generateCSR(t, rsaPK)
|
||||
if err != nil {
|
||||
t.Errorf("failed to generate CA CSR: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
rootRSACR := gen.CertificateRequest("test-root-ca",
|
||||
gen.SetCertificateRequestCSR(caCSR),
|
||||
gen.SetCertificateRequestIsCA(true),
|
||||
gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 60}),
|
||||
)
|
||||
|
||||
// generate a self signed root ca valid for 60d
|
||||
_, rsaPEMCert := generateSelfSignedCertFromCR(t, rootRSACR, rsaPK, time.Hour*24*60)
|
||||
rootRSACASecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "root-ca-secret",
|
||||
Namespace: gen.DefaultTestNamespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
corev1.TLSPrivateKeyKey: rsaPKBytes,
|
||||
corev1.TLSCertKey: rsaPEMCert,
|
||||
},
|
||||
}
|
||||
|
||||
rootRSANoCASecret := rootRSACASecret.DeepCopy()
|
||||
rootRSANoCASecret.Data[corev1.TLSCertKey] = make([]byte, 0)
|
||||
rootRSANoKeySecret := rootRSACASecret.DeepCopy()
|
||||
rootRSANoKeySecret.Data[corev1.TLSPrivateKeyKey] = make([]byte, 0)
|
||||
|
||||
tests := map[string]caFixture{
|
||||
"sign a CertificateRequest": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
CertificateRequest: gen.CertificateRequest("test-cr",
|
||||
gen.SetCertificateRequestIsCA(true),
|
||||
gen.SetCertificateRequestCSR(caCSR),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{rootRSACASecret},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
// we are not expecting key on response
|
||||
CheckFn: noPrivateKeyFieldsSetCheck(rsaPEMCert),
|
||||
Err: false,
|
||||
},
|
||||
"fail to find CA tls key pair": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
CertificateRequest: gen.CertificateRequest("test-cr",
|
||||
gen.SetCertificateRequestIsCA(true),
|
||||
gen.SetCertificateRequestCSR(caCSR),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
CheckFn: func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
err := args[2].(error)
|
||||
notFoundErr := `secret "root-ca-secret" not found`
|
||||
if err == nil || err.Error() != notFoundErr {
|
||||
t.Errorf("unexpected error, exp='%s' got='%+v'", notFoundErr, err)
|
||||
}
|
||||
|
||||
resp := args[1].(*issuer.IssueResponse)
|
||||
if resp != nil {
|
||||
t.Errorf("unexpected response, exp='nil' got='%+v'", resp)
|
||||
}
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
"given bad CSR should fail Certificate generation": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
CertificateRequest: gen.CertificateRequest("test-cr",
|
||||
gen.SetCertificateRequestIsCA(true),
|
||||
gen.SetCertificateRequestCSR([]byte("bad-csr")),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{rootRSACASecret},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
CheckFn: func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
err := args[2].(error)
|
||||
decodeErr := "failed to decode csr from certificate request resource default-unit-test-ns/test-cr"
|
||||
if err == nil || err.Error() != decodeErr {
|
||||
t.Errorf("unexpected error, exp='%s' got='%+v'", decodeErr, err)
|
||||
}
|
||||
|
||||
resp := args[1].(*issuer.IssueResponse)
|
||||
if resp != nil {
|
||||
t.Errorf("unexpected response, exp='nil' got='%+v'", resp)
|
||||
}
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
"no CA certificate should fail a signing": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
CertificateRequest: gen.CertificateRequest("test-cr",
|
||||
gen.SetCertificateRequestIsCA(true),
|
||||
gen.SetCertificateRequestCSR(caCSR),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{rootRSANoCASecret},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
CheckFn: func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
err := args[2].(error)
|
||||
noCertError := "error decoding cert PEM block"
|
||||
if err == nil || err.Error() != noCertError {
|
||||
t.Errorf("unexpected error, exp='%s' got='%+v'", noCertError, err)
|
||||
}
|
||||
|
||||
resp := args[1].(*issuer.IssueResponse)
|
||||
if resp != nil {
|
||||
t.Errorf("unexpected response, exp='nil' got='%+v'", resp)
|
||||
}
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
"no CA key should fail a signing": {
|
||||
Issuer: gen.Issuer("ca-issuer",
|
||||
gen.SetIssuerCA(v1alpha1.CAIssuer{SecretName: "root-ca-secret"}),
|
||||
),
|
||||
CertificateRequest: gen.CertificateRequest("test-cr",
|
||||
gen.SetCertificateRequestIsCA(true),
|
||||
gen.SetCertificateRequestCSR(caCSR),
|
||||
),
|
||||
Builder: &testpkg.Builder{
|
||||
KubeObjects: []runtime.Object{rootRSANoKeySecret},
|
||||
CertManagerObjects: []runtime.Object{},
|
||||
},
|
||||
CheckFn: func(t *testing.T, s *caFixture, args ...interface{}) {
|
||||
err := args[2].(error)
|
||||
noKeyError := "error decoding private key PEM block"
|
||||
if err == nil || err.Error() != noKeyError {
|
||||
t.Errorf("unexpected error, exp='%s' got='%+v'", noKeyError, err)
|
||||
}
|
||||
|
||||
resp := args[1].(*issuer.IssueResponse)
|
||||
if resp != nil {
|
||||
t.Errorf("unexpected response, exp='nil' got='%+v'", resp)
|
||||
}
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if test.Builder == nil {
|
||||
test.Builder = &testpkg.Builder{}
|
||||
}
|
||||
test.Setup(t)
|
||||
crCopy := test.CertificateRequest.DeepCopy()
|
||||
resp, err := test.CA.Sign(test.Ctx, crCopy)
|
||||
if err != nil && !test.Err {
|
||||
t.Errorf("Expected function to not error, but got: %v", err)
|
||||
}
|
||||
if err == nil && test.Err {
|
||||
t.Errorf("Expected function to get an error, but got: %v", err)
|
||||
}
|
||||
test.Finish(t, crCopy, resp, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -33,8 +33,9 @@ type caFixture struct {
|
||||
CA *CA
|
||||
*test.Builder
|
||||
|
||||
Issuer v1alpha1.GenericIssuer
|
||||
Certificate *v1alpha1.Certificate
|
||||
Issuer v1alpha1.GenericIssuer
|
||||
Certificate *v1alpha1.Certificate
|
||||
CertificateRequest *v1alpha1.CertificateRequest
|
||||
|
||||
PreFn func(*testing.T, *caFixture)
|
||||
CheckFn func(*testing.T, *caFixture, ...interface{})
|
||||
|
||||
@ -11,6 +11,7 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/apis/certmanager/v1alpha1:go_default_library",
|
||||
"//pkg/issuer:go_default_library",
|
||||
"//pkg/util/errors:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -23,12 +23,14 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer"
|
||||
)
|
||||
|
||||
// CommonNameForCertificate returns the common name that should be used for the
|
||||
@ -194,6 +196,55 @@ func GenerateTemplate(crt *v1alpha1.Certificate) (*x509.Certificate, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateTemplate will create a x509.Certificate for the given
|
||||
// CertificateRequest resource
|
||||
func GenerateTemplateFromCertificateRequest(cr *v1alpha1.CertificateRequest) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode(cr.Spec.CSRPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode csr from certificate request resource %s/%s",
|
||||
cr.Namespace, cr.Name)
|
||||
}
|
||||
|
||||
csr, err := x509.ParseCertificateRequest(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := csr.CheckSignature(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate serial number: %s", err.Error())
|
||||
}
|
||||
|
||||
certDuration := v1alpha1.DefaultCertificateDuration
|
||||
if cr.Spec.Duration != nil {
|
||||
certDuration = cr.Spec.Duration.Duration
|
||||
}
|
||||
|
||||
keyUsages := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
||||
|
||||
return &x509.Certificate{
|
||||
Version: csr.Version,
|
||||
BasicConstraintsValid: true,
|
||||
SerialNumber: serialNumber,
|
||||
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
|
||||
PublicKey: csr.PublicKey,
|
||||
IsCA: cr.Spec.IsCA,
|
||||
Subject: csr.Subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(certDuration),
|
||||
// see http://golang.org/pkg/crypto/x509/#KeyUsage
|
||||
KeyUsage: keyUsages,
|
||||
DNSNames: csr.DNSNames,
|
||||
IPAddresses: csr.IPAddresses,
|
||||
URIs: csr.URIs,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignCertificate returns a signed x509.Certificate object for the given
|
||||
// *v1alpha1.Certificate crt.
|
||||
// publicKey is the public key of the signee, and signerKey is the private
|
||||
@ -221,6 +272,41 @@ func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, p
|
||||
return pemBytes.Bytes(), cert, err
|
||||
}
|
||||
|
||||
// SignCSRTemplate signs a certificate template usually based upon a CSR. This
|
||||
// function expects all fields to be present in the certificate template,
|
||||
// including it's public key.
|
||||
func SignCSRTemplate(caCerts []*x509.Certificate, caKey crypto.Signer, template *x509.Certificate) (*issuer.IssueResponse, error) {
|
||||
if len(caCerts) == 0 {
|
||||
return nil, errors.New("no CA certificates given to sign CSR template")
|
||||
}
|
||||
|
||||
caCert := caCerts[0]
|
||||
|
||||
certPem, _, err := SignCertificate(template, caCert, template.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
chainPem, err := EncodeX509Chain(caCerts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPem = append(certPem, chainPem...)
|
||||
|
||||
// encode the CA certificate to be bundled in the output
|
||||
caPem, err := EncodeX509(caCerts[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &issuer.IssueResponse{
|
||||
Certificate: certPem,
|
||||
CA: caPem,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EncodeCSR calls x509.CreateCertificateRequest to sign the given CSR template.
|
||||
// It returns a DER encoded signed CSR.
|
||||
func EncodeCSR(template *x509.CertificateRequest, key crypto.Signer) ([]byte, error) {
|
||||
|
||||
@ -54,7 +54,7 @@ func applyDefaults(f *fixture) {
|
||||
runfiles := os.Getenv("TEST_SRCDIR")
|
||||
if f.binariesPath == "" {
|
||||
if runfiles != "" {
|
||||
f.binariesPath = runfiles + "/__main__/hack/bin"
|
||||
f.binariesPath = runfiles + "/cert_manager/hack/bin"
|
||||
}
|
||||
}
|
||||
if f.jsonConfig == nil {
|
||||
|
||||
@ -16,12 +16,13 @@ container_bundle(
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-webhook:{STABLE_DOCKER_TAG}": "//cmd/webhook:image",
|
||||
"{STABLE_DOCKER_REPO}/cert-manager-cainjector:{STABLE_DOCKER_TAG}": "//cmd/cainjector:image",
|
||||
},
|
||||
tags = ["manual"],
|
||||
)
|
||||
|
||||
# we add this rule so users can `bazel build //test/e2e` to run a
|
||||
# platform-independent version of the e2e test binary
|
||||
genrule(
|
||||
name = "e2e.test",
|
||||
name = "e2e",
|
||||
testonly = True,
|
||||
srcs = [":go_default_test"],
|
||||
outs = ["e2e.test"],
|
||||
|
||||
@ -3,6 +3,7 @@ genrule(
|
||||
srcs = ["@io_kubernetes_sigs_kind//:kind"],
|
||||
outs = ["kind"],
|
||||
cmd = "cp $(SRCS) $@",
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
@ -14,6 +15,7 @@ genrule(
|
||||
}),
|
||||
outs = ["kubectl-1.11"],
|
||||
cmd = "cp $(SRCS) $@",
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
@ -25,6 +27,7 @@ genrule(
|
||||
}),
|
||||
outs = ["kubectl-1.12"],
|
||||
cmd = "cp $(SRCS) $@",
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
@ -36,6 +39,7 @@ genrule(
|
||||
}),
|
||||
outs = ["kubectl-1.13"],
|
||||
cmd = "cp $(SRCS) $@",
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
|
||||
load("@io_bazel_rules_docker//go:image.bzl", "go_image")
|
||||
|
||||
# gazelle:ignore
|
||||
|
||||
go_image(
|
||||
name = "image",
|
||||
base = "@static_base//image",
|
||||
embed = ["@org_letsencrypt_pebble//cmd/pebble:go_default_library"],
|
||||
goarch = "amd64",
|
||||
goos = "linux",
|
||||
pure = "on",
|
||||
binary = ":app",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "app",
|
||||
embed = ["@org_letsencrypt_pebble//cmd/pebble:go_default_library"],
|
||||
pure = "on",
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
|
||||
@ -27,6 +27,7 @@ go_library(
|
||||
"//vendor/k8s.io/api/authorization/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
@ -36,6 +37,7 @@ go_library(
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1:go_default_library",
|
||||
"//vendor/sigs.k8s.io/controller-runtime/pkg/client:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -4,10 +4,7 @@ load("@io_bazel_rules_docker//go:image.bzl", "go_image")
|
||||
go_image(
|
||||
name = "image",
|
||||
base = "@static_base//image",
|
||||
embed = [":go_default_library"],
|
||||
goarch = "amd64",
|
||||
goos = "linux",
|
||||
pure = "on",
|
||||
binary = ":sample",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
@ -27,6 +24,7 @@ go_library(
|
||||
go_binary(
|
||||
name = "sample",
|
||||
embed = [":go_default_library"],
|
||||
pure = "on",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
||||
@ -24,12 +24,14 @@ import (
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
api "k8s.io/api/core/v1"
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
||||
crclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
@ -50,6 +52,8 @@ var Scheme = runtime.NewScheme()
|
||||
func init() {
|
||||
kscheme.AddToScheme(Scheme)
|
||||
certmgrscheme.AddToScheme(Scheme)
|
||||
apiext.AddToScheme(Scheme)
|
||||
apireg.AddToScheme(Scheme)
|
||||
}
|
||||
|
||||
// DefaultConfig contains the default shared config the is likely parsed from
|
||||
|
||||
@ -33,6 +33,7 @@ import (
|
||||
|
||||
const invalidACMEURL = "http://not-a-real-acme-url.com"
|
||||
const testingACMEEmail = "test@example.com"
|
||||
const testingACMEEmailAlternative = "another-test@example.com"
|
||||
const testingACMEPrivateKey = "test-acme-private-key"
|
||||
|
||||
var _ = framework.CertManagerDescribe("ACME Issuer", func() {
|
||||
@ -190,4 +191,79 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should handle updates to the email field", func() {
|
||||
acmeURL := pebble.Details().Host
|
||||
acmeIssuer := util.NewCertManagerACMEIssuer(issuerName, acmeURL, testingACMEEmail, testingACMEPrivateKey)
|
||||
|
||||
By("Creating an Issuer")
|
||||
acmeIssuer, err := f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Create(acmeIssuer)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Waiting for Issuer to become Ready")
|
||||
err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
|
||||
acmeIssuer.Name,
|
||||
v1alpha1.IssuerCondition{
|
||||
Type: v1alpha1.IssuerConditionReady,
|
||||
Status: v1alpha1.ConditionTrue,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Verifying the ACME account URI is set")
|
||||
err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
|
||||
acmeIssuer.Name,
|
||||
func(i *v1alpha1.Issuer) (bool, error) {
|
||||
if i.GetStatus().ACMEStatus().URI == "" {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Verifying ACME account private key exists")
|
||||
secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingACMEPrivateKey, metav1.GetOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if len(secret.Data) != 1 {
|
||||
Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data))
|
||||
}
|
||||
|
||||
By("Verifying the ACME account email has been registered")
|
||||
err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
|
||||
acmeIssuer.Name,
|
||||
func(i *v1alpha1.Issuer) (bool, error) {
|
||||
registeredEmail := i.GetStatus().ACMEStatus().LastRegisteredEmail
|
||||
if registeredEmail == testingACMEEmail {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Changing the email field")
|
||||
acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Get(acmeIssuer.Name, metav1.GetOptions{})
|
||||
acmeIssuer.Spec.ACME.Email = testingACMEEmailAlternative
|
||||
acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name).Update(acmeIssuer)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Waiting for Issuer to become Ready")
|
||||
err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
|
||||
acmeIssuer.Name,
|
||||
v1alpha1.IssuerCondition{
|
||||
Type: v1alpha1.IssuerConditionReady,
|
||||
Status: v1alpha1.ConditionTrue,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Verifying the changed ACME account email has been registered")
|
||||
err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
|
||||
acmeIssuer.Name,
|
||||
func(i *v1alpha1.Issuer) (bool, error) {
|
||||
registeredEmail := i.GetStatus().ACMEStatus().LastRegisteredEmail
|
||||
if registeredEmail == testingACMEEmailAlternative {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,9 +14,11 @@ go_library(
|
||||
"//vendor/github.com/onsi/gomega:go_default_library",
|
||||
"//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@ -28,47 +28,211 @@ import (
|
||||
"github.com/jetstack/cert-manager/test/e2e/util"
|
||||
admissionreg "k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
||||
)
|
||||
|
||||
type injectableTest struct {
|
||||
makeInjectable func(namePrefix string) runtime.Object
|
||||
getCAs func(runtime.Object) [][]byte
|
||||
subject string
|
||||
disabled string
|
||||
}
|
||||
|
||||
var _ = framework.CertManagerDescribe("CA Injector", func() {
|
||||
f := framework.NewDefaultFramework("ca-injector")
|
||||
|
||||
issuerName := "inject-cert-issuer"
|
||||
|
||||
Context("for validating webhooks", func() {
|
||||
var hookToCleanUp runtime.Object
|
||||
BeforeEach(func() {
|
||||
By("creating a self-signing issuer")
|
||||
issuer := util.NewCertManagerSelfSignedIssuer(issuerName)
|
||||
issuer.Namespace = f.Namespace.Name
|
||||
Expect(f.CRClient.Create(context.Background(), issuer)).To(Succeed())
|
||||
injectorContext := func(subj string, test *injectableTest) {
|
||||
Context("for "+subj+"s", func() {
|
||||
var toCleanup runtime.Object
|
||||
|
||||
By("Waiting for Issuer to become Ready")
|
||||
err := util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
|
||||
issuerName,
|
||||
certmanager.IssuerCondition{
|
||||
Type: certmanager.IssuerConditionReady,
|
||||
Status: certmanager.ConditionTrue,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
BeforeEach(func() {
|
||||
By("creating a self-signing issuer")
|
||||
issuer := util.NewCertManagerSelfSignedIssuer(issuerName)
|
||||
issuer.Namespace = f.Namespace.Name
|
||||
Expect(f.CRClient.Create(context.Background(), issuer)).To(Succeed())
|
||||
|
||||
By("Waiting for Issuer to become Ready")
|
||||
err := util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1alpha1().Issuers(f.Namespace.Name),
|
||||
issuerName,
|
||||
certmanager.IssuerCondition{
|
||||
Type: certmanager.IssuerConditionReady,
|
||||
Status: certmanager.ConditionTrue,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if toCleanup == nil {
|
||||
return
|
||||
}
|
||||
Expect(f.CRClient.Delete(context.Background(), toCleanup)).To(Succeed())
|
||||
})
|
||||
generalSetup := func(namePrefix string) (runtime.Object, certmanager.Certificate, corev1.Secret) {
|
||||
By("creating a " + subj + " pointing to a cert")
|
||||
injectable := test.makeInjectable(namePrefix)
|
||||
Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed())
|
||||
toCleanup = injectable
|
||||
|
||||
By("creating a certificate")
|
||||
secretName := types.NamespacedName{Name: "serving-certs-data", Namespace: f.Namespace.Name}
|
||||
cert := util.NewCertManagerBasicCertificate("serving-certs", secretName.Name, issuerName, certmanager.IssuerKind, nil, nil)
|
||||
cert.Namespace = f.Namespace.Name
|
||||
Expect(f.CRClient.Create(context.Background(), cert)).To(Succeed())
|
||||
|
||||
By("grabbing the corresponding secret")
|
||||
var secret corev1.Secret
|
||||
Eventually(func() error { return f.CRClient.Get(context.Background(), secretName, &secret) }, "10s", "2s").Should(Succeed())
|
||||
|
||||
By("checking that all webhooks have a populated CA")
|
||||
caData := secret.Data["ca.crt"]
|
||||
expectedLen := len(test.getCAs(injectable))
|
||||
expectedCAs := make([][]byte, expectedLen)
|
||||
for i := range expectedCAs {
|
||||
expectedCAs[i] = caData
|
||||
}
|
||||
Eventually(func() ([][]byte, error) {
|
||||
newInjectable := injectable.DeepCopyObject()
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return test.getCAs(newInjectable), nil
|
||||
}, "10s", "2s").Should(Equal(expectedCAs))
|
||||
|
||||
return injectable, *cert, secret
|
||||
|
||||
AfterEach(func() {
|
||||
if hookToCleanUp == nil {
|
||||
return
|
||||
}
|
||||
Expect(f.CRClient.Delete(context.Background(), hookToCleanUp)).To(Succeed())
|
||||
})
|
||||
|
||||
setupHook := func(hookPrefix string) (admissionreg.ValidatingWebhookConfiguration, certmanager.Certificate, corev1.Secret) {
|
||||
By("creating a validating webhook pointing to a cert")
|
||||
It("should inject the CA data into all CA fields", func() {
|
||||
if test.disabled != "" {
|
||||
Skip(test.disabled)
|
||||
}
|
||||
|
||||
generalSetup("injected")
|
||||
})
|
||||
|
||||
It("should not inject CA into non-annotated objects", func() {
|
||||
if test.disabled != "" {
|
||||
Skip(test.disabled)
|
||||
}
|
||||
By("creating a validating webhook not pointing to a cert")
|
||||
injectable := test.makeInjectable("non-injected")
|
||||
injectable.(metav1.Object).SetAnnotations(map[string]string{}) // wipe out the inject annotation
|
||||
Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed())
|
||||
toCleanup = injectable
|
||||
|
||||
By("expecting the CA data to remain in place")
|
||||
expectedCAs := test.getCAs(injectable)
|
||||
Consistently(func() ([][]byte, error) {
|
||||
newInjectable := injectable.DeepCopyObject()
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return test.getCAs(newInjectable), nil
|
||||
}).Should(Equal(expectedCAs))
|
||||
})
|
||||
|
||||
It("should update data when the certificate changes", func() {
|
||||
if test.disabled != "" {
|
||||
Skip(test.disabled)
|
||||
}
|
||||
injectable, cert, _ := generalSetup("changed")
|
||||
|
||||
By("grabbing the latest copy of the cert")
|
||||
Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: cert.Name, Namespace: cert.Namespace}, &cert)).To(Succeed())
|
||||
|
||||
By("changing the name of the corresponding secret in the cert")
|
||||
secretName := types.NamespacedName{Name: "other-data", Namespace: f.Namespace.Name}
|
||||
cert.Spec.SecretName = secretName.Name
|
||||
Expect(f.CRClient.Update(context.Background(), &cert)).To(Succeed())
|
||||
|
||||
By("grabbing the new secret")
|
||||
var secret corev1.Secret
|
||||
Eventually(func() error { return f.CRClient.Get(context.Background(), secretName, &secret) }, "10s", "2s").Should(Succeed())
|
||||
|
||||
By("verifying that the hooks have the new data")
|
||||
caData := secret.Data["ca.crt"]
|
||||
expectedLen := len(test.getCAs(injectable))
|
||||
expectedCAs := make([][]byte, expectedLen)
|
||||
for i := range expectedCAs {
|
||||
expectedCAs[i] = caData
|
||||
}
|
||||
Eventually(func() ([][]byte, error) {
|
||||
newInjectable := injectable.DeepCopyObject()
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return test.getCAs(newInjectable), nil
|
||||
}, "10s", "2s").Should(Equal(expectedCAs))
|
||||
})
|
||||
|
||||
It("should ignore objects with invalid annotations", func() {
|
||||
if test.disabled != "" {
|
||||
Skip(test.disabled)
|
||||
}
|
||||
By("creating a validating webhook with an invalid cert name")
|
||||
injectable := test.makeInjectable("invalid")
|
||||
injectable.(metav1.Object).SetAnnotations(map[string]string{
|
||||
injctrl.WantInjectAnnotation: "serving-certs", // an invalid annotation
|
||||
})
|
||||
Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed())
|
||||
toCleanup = injectable
|
||||
|
||||
By("expecting the CA data to remain in place")
|
||||
expectedCAs := test.getCAs(injectable)
|
||||
Consistently(func() ([][]byte, error) {
|
||||
newInjectable := injectable.DeepCopyObject()
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return test.getCAs(newInjectable), nil
|
||||
}).Should(Equal(expectedCAs))
|
||||
})
|
||||
|
||||
It("should inject the apiserver CA if the inject-apiserver-ca annotation is present", func() {
|
||||
if test.disabled != "" {
|
||||
Skip(test.disabled)
|
||||
}
|
||||
if len(f.KubeClientConfig.CAData) == 0 {
|
||||
Skip("skipping test as the kube client CA bundle is not set")
|
||||
}
|
||||
By("creating a vaidating webhook with the inject-apiserver-ca annotation")
|
||||
injectable := test.makeInjectable("apiserver-ca")
|
||||
injectable.(metav1.Object).SetAnnotations(map[string]string{
|
||||
injctrl.WantInjectAPIServerCAAnnotation: "true",
|
||||
})
|
||||
Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed())
|
||||
toCleanup = injectable
|
||||
|
||||
By("checking that all webhooks have a populated CA")
|
||||
caData := f.KubeClientConfig.CAData
|
||||
expectedLen := len(test.getCAs(injectable))
|
||||
expectedCAs := make([][]byte, expectedLen)
|
||||
for i := range expectedCAs {
|
||||
expectedCAs[i] = caData
|
||||
}
|
||||
Eventually(func() ([][]byte, error) {
|
||||
newInjectable := injectable.DeepCopyObject()
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return test.getCAs(newInjectable), nil
|
||||
}, "10s", "2s").Should(Equal(expectedCAs))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
injectorContext("validating webhook", &injectableTest{
|
||||
makeInjectable: func(namePrefix string) runtime.Object {
|
||||
someURL := "https://localhost:8675"
|
||||
hook := admissionreg.ValidatingWebhookConfiguration{
|
||||
return &admissionreg.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: hookPrefix + "-hook",
|
||||
Name: namePrefix + "-hook",
|
||||
Annotations: map[string]string{
|
||||
injctrl.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(),
|
||||
},
|
||||
@ -91,157 +255,25 @@ var _ = framework.CertManagerDescribe("CA Injector", func() {
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(f.CRClient.Create(context.Background(), &hook)).To(Succeed())
|
||||
hookToCleanUp = &hook
|
||||
|
||||
By("creating a certificate")
|
||||
secretName := types.NamespacedName{Name: "serving-certs-data", Namespace: f.Namespace.Name}
|
||||
cert := util.NewCertManagerBasicCertificate("serving-certs", secretName.Name, issuerName, certmanager.IssuerKind, nil, nil)
|
||||
cert.Namespace = f.Namespace.Name
|
||||
Expect(f.CRClient.Create(context.Background(), cert)).To(Succeed())
|
||||
|
||||
By("grabbing the corresponding secret")
|
||||
var secret corev1.Secret
|
||||
Eventually(func() error { return f.CRClient.Get(context.Background(), secretName, &secret) }, "10s", "2s").Should(Succeed())
|
||||
|
||||
By("checking that all webhooks have a populated CA")
|
||||
caData := secret.Data["ca.crt"]
|
||||
Eventually(func() ([][]byte, error) {
|
||||
var newHook admissionreg.ValidatingWebhookConfiguration
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: hook.Name}, &newHook); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return [][]byte{newHook.Webhooks[0].ClientConfig.CABundle, newHook.Webhooks[1].ClientConfig.CABundle}, nil
|
||||
}, "10s", "2s").Should(Equal([][]byte{caData, caData}))
|
||||
|
||||
return hook, *cert, secret
|
||||
}
|
||||
|
||||
It("should inject a CA into all webhook slots", func() {
|
||||
setupHook("injected")
|
||||
})
|
||||
|
||||
It("should not inject a CA into webhooks that aren't annotated", func() {
|
||||
By("creating a validating webhook not pointing to a cert")
|
||||
someURL := "https://localhost:8675"
|
||||
hook := admissionreg.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "non-injected-hook",
|
||||
},
|
||||
Webhooks: []admissionreg.Webhook{
|
||||
{
|
||||
Name: "hook1.fake.k8s.io",
|
||||
ClientConfig: admissionreg.WebhookClientConfig{
|
||||
URL: &someURL,
|
||||
CABundle: []byte("ca data 1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "hook2.fake.k8s.io",
|
||||
ClientConfig: admissionreg.WebhookClientConfig{
|
||||
Service: &admissionreg.ServiceReference{
|
||||
Name: "some-svc",
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
CABundle: []byte("ca data 2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
getCAs: func(obj runtime.Object) [][]byte {
|
||||
hook := obj.(*admissionreg.ValidatingWebhookConfiguration)
|
||||
res := make([][]byte, len(hook.Webhooks))
|
||||
for i, webhook := range hook.Webhooks {
|
||||
res[i] = webhook.ClientConfig.CABundle
|
||||
}
|
||||
Expect(f.CRClient.Create(context.Background(), &hook)).To(Succeed())
|
||||
hookToCleanUp = &hook
|
||||
return res
|
||||
},
|
||||
})
|
||||
|
||||
By("expecting the CA data to remain in place")
|
||||
Consistently(func() ([][]byte, error) {
|
||||
var newHook admissionreg.ValidatingWebhookConfiguration
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: hook.Name}, &newHook); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return [][]byte{newHook.Webhooks[0].ClientConfig.CABundle, newHook.Webhooks[1].ClientConfig.CABundle}, nil
|
||||
}).Should(Equal([][]byte{hook.Webhooks[0].ClientConfig.CABundle, hook.Webhooks[1].ClientConfig.CABundle}))
|
||||
})
|
||||
|
||||
It("should update the webhooks when the certificate is changed", func() {
|
||||
hook, cert, _ := setupHook("changed")
|
||||
|
||||
By("grabbing the latest copy of the cert")
|
||||
Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: cert.Name, Namespace: cert.Namespace}, &cert)).To(Succeed())
|
||||
|
||||
By("changing the name of the corresponding secret in the cert")
|
||||
secretName := types.NamespacedName{Name: "other-data", Namespace: f.Namespace.Name}
|
||||
cert.Spec.SecretName = secretName.Name
|
||||
Expect(f.CRClient.Update(context.Background(), &cert)).To(Succeed())
|
||||
|
||||
By("grabbing the new secret")
|
||||
var secret corev1.Secret
|
||||
Eventually(func() error { return f.CRClient.Get(context.Background(), secretName, &secret) }, "10s", "2s").Should(Succeed())
|
||||
|
||||
By("verifying that the hooks have the new data")
|
||||
caData := secret.Data["ca.crt"]
|
||||
Eventually(func() ([][]byte, error) {
|
||||
var newHook admissionreg.ValidatingWebhookConfiguration
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: hook.Name}, &newHook); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return [][]byte{newHook.Webhooks[0].ClientConfig.CABundle, newHook.Webhooks[1].ClientConfig.CABundle}, nil
|
||||
}, "10s", "2s").Should(Equal([][]byte{caData, caData}))
|
||||
})
|
||||
|
||||
It("should ignore webhooks with invalid annotations", func() {
|
||||
By("creating a validating webhook with an invalid cert name")
|
||||
injectorContext("mutating webhook", &injectableTest{
|
||||
makeInjectable: func(namePrefix string) runtime.Object {
|
||||
someURL := "https://localhost:8675"
|
||||
hook := admissionreg.ValidatingWebhookConfiguration{
|
||||
return &admissionreg.MutatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalid-hook",
|
||||
Name: namePrefix + "-hook",
|
||||
Annotations: map[string]string{
|
||||
injctrl.WantInjectAnnotation: "serving-certs",
|
||||
},
|
||||
},
|
||||
Webhooks: []admissionreg.Webhook{
|
||||
{
|
||||
Name: "hook1.fake.k8s.io",
|
||||
ClientConfig: admissionreg.WebhookClientConfig{
|
||||
URL: &someURL,
|
||||
CABundle: []byte("ca data 1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "hook2.fake.k8s.io",
|
||||
ClientConfig: admissionreg.WebhookClientConfig{
|
||||
Service: &admissionreg.ServiceReference{
|
||||
Name: "some-svc",
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
CABundle: []byte("ca data 2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(f.CRClient.Create(context.Background(), &hook)).To(Succeed())
|
||||
hookToCleanUp = &hook
|
||||
|
||||
By("expecting the CA data to remain in place")
|
||||
Consistently(func() ([][]byte, error) {
|
||||
var newHook admissionreg.ValidatingWebhookConfiguration
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: hook.Name}, &newHook); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return [][]byte{newHook.Webhooks[0].ClientConfig.CABundle, newHook.Webhooks[1].ClientConfig.CABundle}, nil
|
||||
}).Should(Equal([][]byte{hook.Webhooks[0].ClientConfig.CABundle, hook.Webhooks[1].ClientConfig.CABundle}))
|
||||
|
||||
})
|
||||
|
||||
It("should inject the apiserver CA if the webhook as the inject-apiserver-ca annotation", func() {
|
||||
if len(f.KubeClientConfig.CAData) == 0 {
|
||||
Skip("skipping test as the kube client CA bundle is not set")
|
||||
}
|
||||
By("creating a vaidating webhook with the inject-apiserver-ca annotation")
|
||||
someURL := "https://localhost:8675"
|
||||
hook := admissionreg.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apiserver-ca-hook",
|
||||
Annotations: map[string]string{
|
||||
injctrl.WantInjectAPIServerCAAnnotation: "true",
|
||||
injctrl.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(),
|
||||
},
|
||||
},
|
||||
Webhooks: []admissionreg.Webhook{
|
||||
@ -262,18 +294,79 @@ var _ = framework.CertManagerDescribe("CA Injector", func() {
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(f.CRClient.Create(context.Background(), &hook)).To(Succeed())
|
||||
hookToCleanUp = &hook
|
||||
},
|
||||
getCAs: func(obj runtime.Object) [][]byte {
|
||||
hook := obj.(*admissionreg.MutatingWebhookConfiguration)
|
||||
res := make([][]byte, len(hook.Webhooks))
|
||||
for i, webhook := range hook.Webhooks {
|
||||
res[i] = webhook.ClientConfig.CABundle
|
||||
}
|
||||
return res
|
||||
},
|
||||
})
|
||||
|
||||
By("checking that all webhooks have a populated CA")
|
||||
caData := f.KubeClientConfig.CAData
|
||||
Eventually(func() ([][]byte, error) {
|
||||
var newHook admissionreg.ValidatingWebhookConfiguration
|
||||
if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: hook.Name}, &newHook); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return [][]byte{newHook.Webhooks[0].ClientConfig.CABundle, newHook.Webhooks[1].ClientConfig.CABundle}, nil
|
||||
}, "10s", "2s").Should(Equal([][]byte{caData, caData}))
|
||||
})
|
||||
// TODO(directxman12): enable ConversionWebhook feature on the test infra,
|
||||
// re-enable this.
|
||||
injectorContext("conversion webhook", &injectableTest{
|
||||
makeInjectable: func(namePrefix string) runtime.Object {
|
||||
someURL := "https://localhost:8675"
|
||||
return &apiext.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "objs." + namePrefix + ".testing.certmanager.k8s.io",
|
||||
Annotations: map[string]string{
|
||||
injctrl.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(),
|
||||
},
|
||||
},
|
||||
Spec: apiext.CustomResourceDefinitionSpec{
|
||||
Group: namePrefix + ".testing.certmanager.k8s.io",
|
||||
Version: "v1",
|
||||
Conversion: &apiext.CustomResourceConversion{
|
||||
Strategy: apiext.WebhookConverter,
|
||||
WebhookClientConfig: &apiext.WebhookClientConfig{
|
||||
URL: &someURL,
|
||||
},
|
||||
},
|
||||
Names: apiext.CustomResourceDefinitionNames{
|
||||
Kind: "Obj",
|
||||
ListKind: "ObjList",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
getCAs: func(obj runtime.Object) [][]byte {
|
||||
crd := obj.(*apiext.CustomResourceDefinition)
|
||||
if crd.Spec.Conversion == nil || crd.Spec.Conversion.WebhookClientConfig == nil {
|
||||
return nil
|
||||
}
|
||||
return [][]byte{crd.Spec.Conversion.WebhookClientConfig.CABundle}
|
||||
},
|
||||
disabled: "ConversionWebhook feature not yet enabled on test infra",
|
||||
})
|
||||
|
||||
injectorContext("api service", &injectableTest{
|
||||
makeInjectable: func(namePrefix string) runtime.Object {
|
||||
return &apireg.APIService{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "v1." + namePrefix + ".testing.certmanager.k8s.io",
|
||||
Annotations: map[string]string{
|
||||
injctrl.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(),
|
||||
},
|
||||
},
|
||||
Spec: apireg.APIServiceSpec{
|
||||
Service: &apireg.ServiceReference{
|
||||
Name: "does-not-exit",
|
||||
Namespace: "default",
|
||||
},
|
||||
Group: namePrefix + ".testing.certmanager.k8s.io",
|
||||
Version: "v1",
|
||||
GroupPriorityMinimum: 1,
|
||||
VersionPriority: 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
getCAs: func(obj runtime.Object) [][]byte {
|
||||
apiSvc := obj.(*apireg.APIService)
|
||||
return [][]byte{apiSvc.Spec.CABundle}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,6 +4,7 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"certificate.go",
|
||||
"certificaterequest.go",
|
||||
"challenge.go",
|
||||
"doc.go",
|
||||
"issuer.go",
|
||||
|
||||
102
test/unit/gen/certificaterequest.go
Normal file
102
test/unit/gen/certificaterequest.go
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2019 The Jetstack cert-manager contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
)
|
||||
|
||||
type CertificateRequestModifier func(*v1alpha1.CertificateRequest)
|
||||
|
||||
func CertificateRequest(name string, mods ...CertificateRequestModifier) *v1alpha1.CertificateRequest {
|
||||
c := &v1alpha1.CertificateRequest{
|
||||
ObjectMeta: ObjectMeta(name),
|
||||
}
|
||||
for _, mod := range mods {
|
||||
mod(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func CertificateRequestFrom(cr *v1alpha1.CertificateRequest, mods ...CertificateRequestModifier) *v1alpha1.CertificateRequest {
|
||||
cr = cr.DeepCopy()
|
||||
for _, mod := range mods {
|
||||
mod(cr)
|
||||
}
|
||||
return cr
|
||||
}
|
||||
|
||||
// SetIssuer sets the CertificateRequest.spec.issuerRef field
|
||||
func SetCertificateRequestIssuer(o v1alpha1.ObjectReference) CertificateRequestModifier {
|
||||
return func(c *v1alpha1.CertificateRequest) {
|
||||
c.Spec.IssuerRef = o
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateRequestCSR(csr []byte) CertificateRequestModifier {
|
||||
return func(cr *v1alpha1.CertificateRequest) {
|
||||
cr.Spec.CSRPEM = csr
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateRequestIsCA(isCA bool) CertificateRequestModifier {
|
||||
return func(cr *v1alpha1.CertificateRequest) {
|
||||
cr.Spec.IsCA = isCA
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateRequestDuration(duration *metav1.Duration) CertificateRequestModifier {
|
||||
return func(cr *v1alpha1.CertificateRequest) {
|
||||
cr.Spec.Duration = duration
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateRequestCA(ca []byte) CertificateRequestModifier {
|
||||
return func(cr *v1alpha1.CertificateRequest) {
|
||||
cr.Status.CA = ca
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateRequestCertificate(cert []byte) CertificateRequestModifier {
|
||||
return func(cr *v1alpha1.CertificateRequest) {
|
||||
cr.Status.Certificate = cert
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateRequestStatusCondition(c v1alpha1.CertificateRequestCondition) CertificateRequestModifier {
|
||||
return func(cr *v1alpha1.CertificateRequest) {
|
||||
if len(cr.Status.Conditions) == 0 {
|
||||
cr.Status.Conditions = []v1alpha1.CertificateRequestCondition{c}
|
||||
return
|
||||
}
|
||||
for i, existingC := range cr.Status.Conditions {
|
||||
if existingC.Type == c.Type {
|
||||
cr.Status.Conditions[i] = c
|
||||
return
|
||||
}
|
||||
}
|
||||
cr.Status.Conditions = append(cr.Status.Conditions, c)
|
||||
}
|
||||
}
|
||||
|
||||
func SetCertificateRequestNamespace(namespace string) CertificateRequestModifier {
|
||||
return func(cr *v1alpha1.CertificateRequest) {
|
||||
cr.ObjectMeta.Namespace = namespace
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user