aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java
blob: 6f80c3da46951d721cea276a4481cfd5904b03ca (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.application.validation.change;

import com.yahoo.config.application.api.ValidationId;
import com.yahoo.config.model.api.ConfigChangeAction;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.container.http.Client;

import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Check that data plane certificates are not removed from a cluster.
 *
 * @author mortent
 */
public class CertificateRemovalChangeValidator implements ChangeValidator {

    private static final Logger logger = Logger.getLogger(CertificateRemovalChangeValidator.class.getName());

    @Override
    public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, DeployState deployState) {
        // Skip for tester applications
        if (current.applicationPackage().getApplicationId().instance().isTester()) return List.of();
        current.getContainerClusters()
                .forEach((clusterId, currentCluster) -> {
                    if(next.getContainerClusters().containsKey(clusterId))
                        validateClients(clusterId,
                                        currentCluster.getClients(),
                                        next.getContainerClusters().get(clusterId).getClients(),
                                        deployState);
                });

        return List.of();
    }

    void validateClients(String clusterId, List<Client> current, List<Client> next, DeployState deployState) {
        List<X509Certificate> currentCertificates = current.stream()
                .filter(client -> !client.internal())
                .map(Client::certificates)
                .flatMap(Collection::stream)
                .toList();
        List<X509Certificate> nextCertificates = next.stream()
                .filter(client -> !client.internal())
                .map(Client::certificates)
                .flatMap(Collection::stream)
                .toList();

        logger.log(Level.FINE, "Certificates for cluster %s: Current: [%s], Next: [%s]"
                .formatted(clusterId,
                           currentCertificates.stream().map(cert -> cert.getSubjectX500Principal().getName()).collect(Collectors.joining(", ")),
                           nextCertificates.stream().map(cert -> cert.getSubjectX500Principal().getName()).collect(Collectors.joining(", "))));

        List<X509Certificate> missingCerts = currentCertificates.stream().filter(cert -> !nextCertificates.contains(cert)).toList();
        if (!missingCerts.isEmpty()) {
            deployState.validationOverrides().invalid(ValidationId.certificateRemoval,
                              "Data plane certificate(s) from cluster '" + clusterId + "' is removed " +
                              "(removed certificates: " + missingCerts.stream().map(x509Certificate -> x509Certificate.getSubjectX500Principal().getName()).toList() + ") " +
                              "This can cause client connection issues.",
                              deployState.now());
        }

    }
}