Skip to content

Commit

Permalink
✨ Adds cert-manager operator system-x
Browse files Browse the repository at this point in the history
  • Loading branch information
mcarlett committed Dec 12, 2024
1 parent 52fb449 commit da7d17b
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 0 deletions.
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@
<artifactId>system-x-cassandra</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>software.tnb</groupId>
<artifactId>system-x-cert-manager</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>software.tnb</groupId>
<artifactId>system-x-common</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions system-x/services/all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@
<groupId>software.tnb</groupId>
<artifactId>system-x-cassandra</artifactId>
</dependency>
<dependency>
<groupId>software.tnb</groupId>
<artifactId>system-x-cert-manager</artifactId>
</dependency>
<dependency>
<groupId>software.tnb</groupId>
<artifactId>system-x-cryostat</artifactId>
Expand Down
37 changes: 37 additions & 0 deletions system-x/services/cert-manager/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>system-x-services</artifactId>
<groupId>software.tnb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<properties>
<velocity.version>2.4.1</velocity.version>
</properties>
<modelVersion>4.0.0</modelVersion>

<artifactId>system-x-cert-manager</artifactId>
<version>1.0-SNAPSHOT</version>
<name>TNB :: System-X :: Services :: Cert Manager</name>

<dependencies>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package software.tnb.certmanager.resource.opesnhift;

import software.tnb.certmanager.service.CertManager;
import software.tnb.common.deployment.ReusableOpenshiftDeployable;
import software.tnb.common.deployment.WithCustomResource;
import software.tnb.common.deployment.WithOperatorHub;
import software.tnb.common.openshift.OpenshiftClient;
import software.tnb.common.utils.WaitUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.auto.service.AutoService;

import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;

import cz.xtf.core.openshift.helpers.ResourceParsers;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.api.model.Pod;

@AutoService(CertManager.class)
public class OpenshiftCertManager extends CertManager implements ReusableOpenshiftDeployable, WithOperatorHub, WithCustomResource {

private static final Logger LOG = LoggerFactory.getLogger(OpenshiftCertManager.class);

@Override
public void undeploy() {
OpenshiftClient.get().genericKubernetesResources(apiVersion(), kind()).delete();
WaitUtils.waitFor(() -> servicePod() == null, "Waiting until the pod is removed");
deleteSubscription(() -> OpenshiftClient.get().getLabeledPods("name", "cert-manager-operator").isEmpty());
}

/**
* Open all resources needed after the service is deployed - initialize clients and stuff.
*/
@Override
public void openResources() {
}

/**
* Close all resources used after before the service is undeployed.
*/
@Override
public void closeResources() {
//do nothing
}

@Override
public void create() {
LOG.debug("Creating Cert Manager instance");
// Create subscription
createSubscription();

WaitUtils.waitFor(() -> !OpenshiftClient.get()
.pods().inNamespace(targetNamespace())
.withLabel("name", "cert-manager-operator").list().getItems().isEmpty()
, "Wait for the operator has been installed");

}

@Override
public boolean isReady() {
return ResourceParsers.isPodReady(OpenshiftClient.get()
.pods().inNamespace(targetNamespace())
.withLabel("name", "cert-manager-operator").list().getItems().get(0));
}

@Override
public boolean isDeployed() {
return isReady();
}

@Override
public Predicate<Pod> podSelector() {
return p -> OpenshiftClient.get().hasLabels(p, Map.of("name", "cert-manager-operator"));
}

@Override
public void cleanup() {
//do nothing
}

@Override
public String operatorChannel() {
return "stable-v1";
}

@Override
public String operatorName() {
return "openshift-cert-manager-operator";
}

@Override
public String kind() {
return "CertManager";
}

@Override
public String apiVersion() {
return "operator.openshift.io/v1alpha1";
}

@Override
public GenericKubernetesResource customResource() {
return null;
}

@Override
public String targetNamespace() {
return "cert-manager-operator";
}

@Override
public String subscriptionName() {
return "openshift-cert-manager-operator";
}

@Override
public void deleteSubscription(BooleanSupplier waitCondition) {
//want to keep the operator
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package software.tnb.certmanager.service;

import software.tnb.certmanager.validation.CertManagerValidation;
import software.tnb.common.account.NoAccount;
import software.tnb.common.client.NoClient;
import software.tnb.common.service.Service;

import java.util.Optional;

public abstract class CertManager extends Service<NoAccount, NoClient, CertManagerValidation> {

@Override
public CertManagerValidation validation() {
validation = Optional.ofNullable(validation)
.orElseGet(CertManagerValidation::new);
return validation;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package software.tnb.certmanager.validation;

import software.tnb.common.openshift.OpenshiftClient;
import software.tnb.common.validation.Validation;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.yaml.snakeyaml.Yaml;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import io.fabric8.kubernetes.api.model.GenericKubernetesResourceBuilder;
import io.fabric8.kubernetes.api.model.ServiceAccount;
import io.fabric8.kubernetes.api.model.ServiceAccountBuilder;
import io.fabric8.kubernetes.api.model.rbac.PolicyRule;
import io.fabric8.kubernetes.api.model.rbac.Role;
import io.fabric8.kubernetes.api.model.rbac.RoleBinding;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
import io.fabric8.kubernetes.api.model.rbac.RoleBuilder;
import io.fabric8.kubernetes.api.model.rbac.RoleRef;
import io.fabric8.kubernetes.api.model.rbac.Subject;
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext;

public class CertManagerValidation implements Validation {

private static final CustomResourceDefinitionContext ISSUER_CTX = new CustomResourceDefinitionContext
.Builder()
.withGroup("cert-manager.io")
.withKind("Issuer")
.withName("issuers.cert-manager.io")
.withPlural("issuers")
.withScope("Namespaced")
.withVersion("v1")
.build();

private static final CustomResourceDefinitionContext CERTIFICATE_CTX = new CustomResourceDefinitionContext
.Builder()
.withGroup("cert-manager.io")
.withKind("Certificate")
.withName("certificates.cert-manager.io")
.withPlural("certificates")
.withScope("Namespaced")
.withVersion("v1")
.build();

/**
* Creates self-signed issuer in the current namespace
*/
public void createSelfSignedIssuer() {
OpenshiftClient.get().genericKubernetesResources(ISSUER_CTX)
.inNamespace(OpenshiftClient.get().getNamespace())
.resource(new GenericKubernetesResourceBuilder()
.withKind(ISSUER_CTX.getKind())
.withNewMetadata()
.withName("selfsigned-issuer")
.endMetadata()
.withAdditionalProperties(Map.of("spec", Map.of(
"selfSigned", Map.of()
)))
.build()
).create();
}

/**
* Creates self-signed certificate in the current namespace, it requires createSelfSignedIssuer to be called
* @param name String, the name of the certificate CR
* @param secretName String, the name of the secret that will contain the certificates
* @param commonName String, the common name assigned to the certificate
* @param usages List, the list of the usages according
* to <a href="https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.KeyUsage">KeyUsage</a>
* @param dnsNames List, the list of the associated names
* @param passwordSecretName String, the name of the secret, containing the key `password` to use as password
*/
public void createSelfSignedCertificate(String name, String secretName, String commonName, List<String> usages
, List<String> dnsNames, String passwordSecretName) {
VelocityEngine engine = new VelocityEngine();
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
engine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
engine.init();
Template template = engine.getTemplate("cert-manager/certificate-template.vm");
VelocityContext context = new VelocityContext();
context.put("name", name);
context.put("secretName", secretName);
context.put("commonName", commonName);
context.put("namespace", OpenshiftClient.get().getNamespace());
context.put("usagesList", usages);
context.put("dnsNameList", dnsNames);
context.put("passwordSecretRef", passwordSecretName);
StringWriter writer = new StringWriter();
template.merge(context, writer);
Map<String, Object> spec = new Yaml().load(writer.toString());

OpenshiftClient.get().genericKubernetesResources(CERTIFICATE_CTX)
.inNamespace(OpenshiftClient.get().getNamespace())
.resource(new GenericKubernetesResourceBuilder()
.withKind(CERTIFICATE_CTX.getKind())
.withNewMetadata()
.withName(name)
.endMetadata()
.withAdditionalProperties(spec)
.build()
).create();
}

/**
* Creates a service account enabled to read secrets in the current namespace.
* A new service account, role and role binding will be created
* @param serviceAccount String, the name of the service account
*/
public void createSecretViewer(String serviceAccount) {
final String roleName = "secret-viewer";
final String roleBingingName = "sa-secret-viewer";

ServiceAccount sa = new ServiceAccountBuilder()
.withNewMetadata().withName(serviceAccount).endMetadata()
.withAutomountServiceAccountToken(false)
.build();
OpenshiftClient.get().serviceAccounts().inNamespace(OpenshiftClient.get().getNamespace()).resource(sa).create();

List<PolicyRule> policyRuleList = new ArrayList<>();
PolicyRule endpoints = new PolicyRule();
endpoints.setApiGroups(List.of(""));
endpoints.setResources(List.of("secrets"));
endpoints.setVerbs(Arrays.asList("get", "list", "watch"));
policyRuleList.add(endpoints);
Role roleCreated = new RoleBuilder()
.withNewMetadata().withName(roleName).withNamespace(OpenshiftClient.get().getNamespace()).endMetadata()
.addAllToRules(policyRuleList)
.build();
OpenshiftClient.get().rbac().roles().resource(roleCreated).create();

List<Subject> subjects = new ArrayList<>();
Subject subject = new Subject();
subject.setKind("ServiceAccount");
subject.setName(sa.getMetadata().getName());
subject.setNamespace(OpenshiftClient.get().getNamespace());
subjects.add(subject);
RoleRef roleRef = new RoleRef();
roleRef.setApiGroup("rbac.authorization.k8s.io");
roleRef.setKind("Role");
roleRef.setName(roleCreated.getMetadata().getName());
RoleBinding roleBindingCreated = new RoleBindingBuilder()
.withNewMetadata().withName(roleBingingName).withNamespace(OpenshiftClient.get().getNamespace()).endMetadata()
.withRoleRef(roleRef)
.addAllToSubjects(subjects)
.build();
OpenshiftClient.get().rbac().roleBindings().resource(roleBindingCreated).create();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
spec:
secretName: $secretName
duration: 2160h
renewBefore: 360h
subject:
organizations:
- $namespace

commonName: $commonName
isCA: false
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
usages:
#foreach($usage in $usagesList)
- $usage
#end

dnsNames:
#foreach($dnsName in $dnsNameList)
- $dnsName
#end
issuerRef:
name: selfsigned-issuer
kind: Issuer
group: cert-manager.io

keystores:
jks:
create: true
passwordSecretRef:
name: $passwordSecretRef
key: password
Loading

0 comments on commit da7d17b

Please sign in to comment.