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}