001/*
002 * VM-Operator
003 * Copyright (C) 2023 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.vmoperator.manager;
020
021import freemarker.template.TemplateException;
022import io.kubernetes.client.openapi.ApiException;
023import io.kubernetes.client.openapi.models.V1ObjectMeta;
024import io.kubernetes.client.openapi.models.V1Secret;
025import io.kubernetes.client.util.generic.options.ListOptions;
026import java.io.IOException;
027import java.security.NoSuchAlgorithmException;
028import java.security.SecureRandom;
029import java.util.Map;
030import java.util.logging.Logger;
031import org.jdrupes.vmoperator.common.K8sV1SecretStub;
032import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
033import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET;
034import static org.jdrupes.vmoperator.manager.Constants.DATA_DISPLAY_PASSWORD;
035import static org.jdrupes.vmoperator.manager.Constants.DATA_PASSWORD_EXPIRY;
036import org.jdrupes.vmoperator.manager.events.VmChannel;
037import org.jdrupes.vmoperator.manager.events.VmDefChanged;
038import org.jdrupes.vmoperator.util.DataPath;
039import org.jose4j.base64url.Base64;
040
041/**
042 * Delegee for reconciling the display secret
043 */
044@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
045/* default */ class DisplaySecretReconciler {
046
047    protected final Logger logger = Logger.getLogger(getClass().getName());
048
049    /**
050     * Reconcile.
051     *
052     * @param event the event
053     * @param model the model
054     * @param channel the channel
055     * @throws IOException Signals that an I/O exception has occurred.
056     * @throws TemplateException the template exception
057     * @throws ApiException the api exception
058     */
059    public void reconcile(VmDefChanged event,
060            Map<String, Object> model, VmChannel channel)
061            throws IOException, TemplateException, ApiException {
062        // Secret needed at all?
063        var display = event.vmDefinition().fromVm("display").get();
064        if (!DataPath.<Boolean> get(display, "spice", "generateSecret")
065            .orElse(true)) {
066            return;
067        }
068
069        // Check if exists
070        var vmDef = event.vmDefinition();
071        ListOptions options = new ListOptions();
072        options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ","
073            + "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + ","
074            + "app.kubernetes.io/instance=" + vmDef.name());
075        var stubs = K8sV1SecretStub.list(channel.client(), vmDef.namespace(),
076            options);
077        if (!stubs.isEmpty()) {
078            return;
079        }
080
081        // Create secret
082        var secret = new V1Secret();
083        secret.setMetadata(new V1ObjectMeta().namespace(vmDef.namespace())
084            .name(vmDef.name() + "-" + COMP_DISPLAY_SECRET)
085            .putLabelsItem("app.kubernetes.io/name", APP_NAME)
086            .putLabelsItem("app.kubernetes.io/component", COMP_DISPLAY_SECRET)
087            .putLabelsItem("app.kubernetes.io/instance", vmDef.name()));
088        secret.setType("Opaque");
089        SecureRandom random = null;
090        try {
091            random = SecureRandom.getInstanceStrong();
092        } catch (NoSuchAlgorithmException e) { // NOPMD
093            // "Every implementation of the Java platform is required
094            // to support at least one strong SecureRandom implementation."
095        }
096        byte[] bytes = new byte[16];
097        random.nextBytes(bytes);
098        var password = Base64.encode(bytes);
099        secret.setStringData(Map.of(DATA_DISPLAY_PASSWORD, password,
100            DATA_PASSWORD_EXPIRY, "now"));
101        K8sV1SecretStub.create(channel.client(), secret);
102    }
103
104}