Skip to content

Commit

Permalink
Remove Kyverno dependency (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
bastjan authored Sep 20, 2024
1 parent 9b22cc6 commit ea08bb1
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 126 deletions.
6 changes: 6 additions & 0 deletions class/defaults.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
parameters:
openshift4_console:
images:
oc:
registry: quay.io
repository: appuio/oc
tag: v4.15

namespace: openshift-console
namespace_annotations:
openshift.io/node-selector: ''
Expand Down
44 changes: 0 additions & 44 deletions component/main.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -172,48 +172,6 @@ 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 == 'Patch' 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
po.Patch(
target,
patch,
patchstrategy='application/merge-patch+json'
)
];

{
'00_namespace': kube.Namespace(params.namespace) {
metadata+: {
Expand Down Expand Up @@ -247,6 +205,4 @@ local openshiftConfigNsAnnotationPatch =
faviconRoute,
[if consoleRoutePatch != null then '20_ingress_config_patch']:
consoleRoutePatch,
[if openshiftConfigNsAnnotationPatch != null then '20_openshift_config_ns_annotation_patch']:
openshiftConfigNsAnnotationPatch,
}
24 changes: 24 additions & 0 deletions component/scripts/reconcile-console-secret.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail

test -n "${SECRET_NAME:-}" || (echo "SECRET_NAME is required" && exit 1)

source_namespace="openshift-console"
target_namespace="openshift-config"

# # Wait for the secret to be created before trying to get it.
# # TODO: --for=create is included with OCP 4.17
# kubectl -n "${source_namespace}" wait secret "${SECRET_NAME}" --for=create --timeout=30m
echo "Waiting for secret ${SECRET_NAME} to be created"
while test -z "$(kubectl -n "${source_namespace}" get secret "${SECRET_NAME}" --ignore-not-found -oname)" ; do
printf "."
sleep 1
done
printf "\n"

# When using -w flag kubectl returns the secret once on startup and then again when it changes.
kubectl -n "${source_namespace}" get secret "${SECRET_NAME}" -ojson -w | jq -c --unbuffered | while read -r secret ; do
echo "Syncing secret: $(printf "%s" "$secret" | jq -r '.metadata.name')"

kubectl -n "$target_namespace" apply --server-side -f <(printf "%s" "$secret" | jq '{"apiVersion": .apiVersion, "kind": .kind, "metadata": {"name": .metadata.name}, "type": .type, "data": .data}')
done
153 changes: 120 additions & 33 deletions component/tls.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ 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 @@ -36,52 +35,141 @@ local secrets = std.filter(
]
);

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 sa = kube.ServiceAccount('openshift4-console-sync-' + c) {
metadata+: {
namespace: params.namespace,
},
};
local sourceNsRole = kube.Role('openshift4-console-sync-' + c) {
metadata+: {
namespace: params.namespace,
},
rules: [
{
apiGroups: [ '' ],
resources: [ 'secrets' ],
verbs: [ 'get', 'list', 'watch' ],
},
],
};
local targetNsRole = kube.Role('openshift4-console-sync-' + c) {
metadata+: {
namespace: 'openshift-config',
},
rules: [
{
apiGroups: [ '' ],
resources: [ 'secrets' ],
verbs: [ 'get', 'update', 'patch' ],
},
],
};

[
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.
// We copy the resulting secret to namespace 'openshift-config', 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,
},
kube.ConfigMap('openshift4-console-sync-' + c) {
metadata+: {
namespace: params.namespace,
},
data: {
'reconcile-console-secret.sh': (importstr 'scripts/reconcile-console-secret.sh'),
},
},
sa,
sourceNsRole,
targetNsRole,
kube.RoleBinding('openshift4-console-sync-' + c) {
metadata+: {
namespace: sourceNsRole.metadata.namespace,
},
subjects_: [ sa ],
roleRef_: sourceNsRole,
},
kube.RoleBinding('openshift4-console-sync-' + c) {
metadata+: {
namespace: targetNsRole.metadata.namespace,
},
subjects_: [ sa ],
roleRef_: targetNsRole,
},
kube.Deployment('openshift4-console-sync-' + c) {
metadata+: {
namespace: params.namespace,
},
spec+: {
strategy: {
type: 'Recreate',
},
replicas: 1,
selector: {
matchLabels: {
app: 'openshift4-console-sync-' + c,
},
},
template+: {
metadata: {
labels: {
app: 'openshift4-console-sync-' + c,
},
generate: {
kind: 'Secret',
name: c,
namespace: '{{request.object.metadata.name}}',
synchronize: true,
clone: {
namespace: params.namespace,
name: c,
},
spec+: {
serviceAccountName: 'openshift4-console-sync-' + c,
containers: [
{
name: 'sync',
image: '%(registry)s/%(repository)s:%(tag)s' % params.images.oc,
workingDir: '/export',
env: [
{
name: 'SECRET_NAME',
value: c,
},
{
name: 'HOME',
value: '/export',
},
],
command: [
'/scripts/reconcile-console-secret.sh',
],
volumeMounts: [
{
name: 'export',
mountPath: '/export',
},
{
name: 'scripts',
mountPath: '/scripts',
},
],
},
},
],
volumes: [
{
name: 'scripts',
configMap: {
name: 'openshift4-console-sync-' + c,
defaultMode: 365, // 365 = 0555
},
},
{
name: 'export',
emptyDir: {},
},
],
},
],
},
},
},
];
Expand All @@ -104,5 +192,4 @@ local certs =
{
certs: certs,
secrets: secrets,
kyvernoAnnotation: kyvernoAnnotation,
}
3 changes: 1 addition & 2 deletions docs/modules/ROOT/pages/references/parameters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ The dictionary values are then directly directly merged into the mostly empty `C
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.
However, since OpenShift requires that custom TLS secrets for the OpenShift console are stored in namespace `openshift-config`, we deploy a script to clone the TLS secret created by cert-manager into namespace `openshift-config` for each `Certificate` resource.


== Example: Custom hostname in cluster's app domain
Expand Down
5 changes: 0 additions & 5 deletions tests/custom-route-managed-tls.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
applications:
- kyverno
parameters:
kapitan:
dependencies:
Expand All @@ -9,9 +7,6 @@ parameters:
- type: https
source: https://raw.githubusercontent.com/projectsyn/component-patch-operator/v1.2.0/lib/patch-operator.libsonnet
output_path: vendor/lib/patch-operator.libsonnet
- type: https
source: https://raw.githubusercontent.com/projectsyn/component-kyverno/v1.4.0/lib/kyverno.libsonnet
output_path: vendor/lib/kyverno.libsonnet

patch_operator:
patch_serviceaccount:
Expand Down
Loading

0 comments on commit ea08bb1

Please sign in to comment.