Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Kyverno dependency #71

Merged
merged 3 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
}
16 changes: 16 additions & 0 deletions component/scripts/reconcile-console-secret.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
set -euo pipefail

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

target_namespace="openshift-config"

# Wait for the secret to be created before trying to get it.
kubectl -n openshift-console wait secret "${SECRET_NAME}" --for=create --timeout=30m

# When using -w flag kubectl returns the secret once on startup and then again when it changes.
kubectl -n openshift-console 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,
},
strategy: {
type: 'Recreate',
},
spec+: {
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
Loading