Skip to content

Commit

Permalink
Merge pull request #22 from appuio/fix/custom-cert-manager-cert
Browse files Browse the repository at this point in the history
Create cert-manager Certificates in namespace `openshift-console`
  • Loading branch information
simu authored Feb 11, 2022
2 parents 4c37a22 + 7c250b3 commit 9a6ca91
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 29 deletions.
44 changes: 44 additions & 0 deletions component/main.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,48 @@ local consoleRoutePatch =

local tls = import 'tls.libsonnet';

// If we deploy cert-manager Certificates, we annotate namespace
// openshift-config with the `kyvernoAnnotation` defined in `tls.libsonnet`
// through a ResourceLocker patch. This triggers the the Kyverno policy to
// copy the cert-manager TLS secrets into namespace openshift-config.
//
// We add the ResourceLocker patch to ArgoCD sync-wave 5, so it's guaranteed
// to be applied in the cluster after the certificate has been issued and
// before the custom openshift console route config is applied.
//
// NOTE: Due to the current implementation of the resource locker component
// library this prevents other components from also providing ResourceLocker
// patches for the `openshift-config` namespace.
local openshiftConfigNsAnnotationPatch =
local needsPatch = hostname != null && std.length(tls.certs) > 0;
if needsPatch then
local target = kube.Namespace('openshift-config');
local patch = {
metadata: {
annotations: tls.kyvernoAnnotation,
},
};
[
if obj.kind == 'ResourceLocker' then
obj {
metadata+: {
annotations+: {
// Annotate namespace openshift-config before we configure the
// route certificate, see patch above
'argocd.argoproj.io/sync-wave': '5',
},
},
}
else
obj
for obj in
rl.Patch(
target,
patch,
patchstrategy='application/merge-patch+json'
)
];

{
'00_namespace': kube.Namespace(params.namespace) {
metadata+: {
Expand Down Expand Up @@ -184,4 +226,6 @@ local tls = import 'tls.libsonnet';
},
[if !oldConfig && consoleRoutePatch != null then '20_ingress_config_patch']:
consoleRoutePatch,
[if openshiftConfigNsAnnotationPatch != null then '20_openshift_config_ns_annotation_patch']:
openshiftConfigNsAnnotationPatch,
}
81 changes: 65 additions & 16 deletions component/tls.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local cm = import 'lib/cert-manager.libsonnet';
local com = import 'lib/commodore.libjsonnet';
local kap = import 'lib/kapitan.libjsonnet';
local kube = import 'lib/kube.libjsonnet';
local kyverno = import 'lib/kyverno.libsonnet';

local inv = kap.inventory();
local params = inv.parameters.openshift4_console;
Expand Down Expand Up @@ -35,25 +36,73 @@ local secrets = std.filter(
]
);

local certs = std.filter(
function(it) it != null,
local kyvernoAnnotation = {
'syn.tools/openshift4-console': 'secret-target-namespace',
};

local makeCert(c, cert) =
assert
std.member(inv.applications, 'kyverno') :
'You need to add component `kyverno` to the cluster to be able to deploy cert-manager Certificate resources for the the openshift web console.';
[
local cert = params.cert_manager_certs[c];
if cert != null then
cm.cert(c) {
metadata+: {
// Certificates must be deployed in namespace openshift-config
namespace: 'openshift-config',
},
spec+: {
secretName: '%s' % c,
},
} + com.makeMergeable(cert)
for c in std.objectFields(params.cert_manager_certs)
]
);
cm.cert(c) {
metadata+: {
// Certificate must be deployed in the same namespace as the web
// console, otherwise OpenShift won't admit the HTTP01 solver route.
// We copy the resulting secret to namespace 'openshift-config' with
// Kyverno, see below.
namespace: params.namespace,
},
spec+: {
secretName: '%s' % c,
},
} + com.makeMergeable(cert),
kyverno.ClusterPolicy('openshift4-console-sync-' + c) {
spec: {
rules: [
{
name: 'Sync "%s" certificate secret to openshift-config' % c,
match: {
resources: {
kinds: [ 'Namespace' ],
// We copy the created TLS secret into all namespaces which
// have the annotation specified in `kyvernoAnnotation`.
annotations: kyvernoAnnotation,
},
},
generate: {
kind: 'Secret',
name: c,
namespace: '{{request.object.metadata.name}}',
synchronize: true,
clone: {
namespace: params.namespace,
name: c,
},
},
},
],
},
},
];

local certs =
std.foldl(
function(arr, e) arr + e,
std.filter(
function(it) it != null,
[
local cert = params.cert_manager_certs[c];
if cert != null then
makeCert(c, cert)
for c in std.objectFields(params.cert_manager_certs)
],
),
[]
);

{
certs: certs,
secrets: secrets,
kyvernoAnnotation: kyvernoAnnotation,
}
6 changes: 6 additions & 0 deletions docs/modules/ROOT/pages/references/parameters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ This allows users to remove certificates which were configured higher up in the
The dictionary keys are used as `metadata.name` and `spec.secretName` for the resulting `Certificate` resources.
The dictionary values are then directly directly merged into the mostly empty `Certificate` resources.

OpenShift won't admit the route for the HTTP01 solver pod unless the `Certificate` resources are deployed in the same namespace as the web console.
This behavior is caused by a security feature in the OpenShift ingress controller operator to not allow malicious actors to abuse hostnames which are already in use in other namespaces.

However, since OpenShift requires that custom TLS secrets for the OpenShift console are stored in namespace `openshift-config`, we deploy a Kyverno policy to clone the TLS secret created by cert-manager into namespace `openshift-config` for each `Certificate` resource.
Because of that, the component requires that Kyverno is installed on the cluster via the https://hub.syn.tools/kyverno/[Commodore component `kyverno`], when `Certificate` resources are configured in the hierarchy.


== Example: Custom hostname in cluster's app domain

Expand Down
7 changes: 6 additions & 1 deletion tests/custom-route-managed-tls.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
applications:
- kyverno
parameters:
kapitan:
dependencies:
- type: https
source: https://raw.githubusercontent.com/projectsyn/component-cert-manager/v2.2.0/lib/cert-manager.libsonnet
output_path: vendor/lib/cert-manager.libsonnet
- type: https
source: https://raw.githubusercontent.com/projectsyn/component-resource-locker/v2.1.0/lib/resource-locker.libjsonnet
source: https://raw.githubusercontent.com/projectsyn/component-resource-locker/v2.3.2/lib/resource-locker.libjsonnet
output_path: vendor/lib/resource-locker.libjsonnet
- type: https
source: https://raw.githubusercontent.com/projectsyn/component-kyverno/v1.4.0/lib/kyverno.libsonnet
output_path: vendor/lib/kyverno.libsonnet

resource_locker:
namespace: syn-resource-locker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,36 @@ metadata:
labels:
name: console-cluster-example-org-tls
name: console-cluster-example-org-tls
namespace: openshift-config
namespace: openshift-console
spec:
dnsNames:
- console.cluster.example.org
issuerRef:
kind: ClusterIssuer
name: letsencrypt-staging
secretName: console-cluster-example-org-tls
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
annotations: {}
labels:
name: openshift4-console-sync-console-cluster-example-org-tls
name: openshift4-console-sync-console-cluster-example-org-tls
spec:
rules:
- generate:
clone:
name: console-cluster-example-org-tls
namespace: openshift-console
kind: Secret
name: console-cluster-example-org-tls
namespace: '{{request.object.metadata.name}}'
synchronize: true
match:
resources:
annotations:
syn.tools/openshift4-console: secret-target-namespace
kinds:
- Namespace
name: Sync "console-cluster-example-org-tls" certificate secret to openshift-config
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ apiVersion: v1
kind: ServiceAccount
metadata:
labels:
name: cluster-manager
name: cluster-manager
name: ingress-config-openshift-io-cluster-manager
name: ingress-config-openshift-io-cluster-manager
namespace: syn-resource-locker
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
name: syn-resource-locker-cluster-manager
name: syn-resource-locker-cluster-manager
name: syn-resource-locker-ingress-config-openshift-io-cluster-manager
name: syn-resource-locker-ingress-config-openshift-io-cluster-manager
rules:
- apiGroups:
- config.openshift.io
Expand All @@ -27,15 +27,15 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
name: syn-resource-locker-cluster-manager
name: syn-resource-locker-cluster-manager
name: syn-resource-locker-ingress-config-openshift-io-cluster-manager
name: syn-resource-locker-ingress-config-openshift-io-cluster-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: syn-resource-locker-cluster-manager
name: syn-resource-locker-ingress-config-openshift-io-cluster-manager
subjects:
- kind: ServiceAccount
name: cluster-manager
name: ingress-config-openshift-io-cluster-manager
namespace: syn-resource-locker
---
apiVersion: redhatcop.redhat.io/v1alpha1
Expand All @@ -45,8 +45,8 @@ metadata:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
argocd.argoproj.io/sync-wave: '10'
labels:
name: cluster
name: cluster
name: ingress-config-openshift-io-cluster
name: ingress-config-openshift-io-cluster
namespace: syn-resource-locker
spec:
patches:
Expand All @@ -60,4 +60,4 @@ spec:
kind: Ingress
name: cluster
serviceAccountRef:
name: cluster-manager
name: ingress-config-openshift-io-cluster-manager
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
name: namespace-openshift-config-manager
name: namespace-openshift-config-manager
namespace: syn-resource-locker
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
name: syn-resource-locker-namespace-openshift-config-manager
name: syn-resource-locker-namespace-openshift-config-manager
rules:
- apiGroups:
- ''
resources:
- namespaces
verbs:
- get
- list
- patch
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
name: syn-resource-locker-namespace-openshift-config-manager
name: syn-resource-locker-namespace-openshift-config-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: syn-resource-locker-namespace-openshift-config-manager
subjects:
- kind: ServiceAccount
name: namespace-openshift-config-manager
namespace: syn-resource-locker
---
apiVersion: redhatcop.redhat.io/v1alpha1
kind: ResourceLocker
metadata:
annotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
argocd.argoproj.io/sync-wave: '5'
labels:
name: namespace-openshift-config
name: namespace-openshift-config
namespace: syn-resource-locker
spec:
patches:
- id: patch1
patchTemplate: "\"metadata\":\n \"annotations\":\n \"syn.tools/openshift4-console\"\
: \"secret-target-namespace\""
patchType: application/merge-patch+json
targetObjectRef:
apiVersion: v1
kind: Namespace
name: openshift-config
serviceAccountRef:
name: namespace-openshift-config-manager

0 comments on commit 9a6ca91

Please sign in to comment.