aboutsummaryrefslogtreecommitdiffstats
path: root/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImplTest.java
blob: 75dc42cd4a6994bffa25b60139abd40025b07d74 (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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identityprovider.client;

import com.yahoo.container.core.identity.IdentityConfig;
import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException;
import com.yahoo.jdisc.Metric;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyStoreBuilder;
import com.yahoo.security.KeyStoreType;
import com.yahoo.security.KeyStoreUtils;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.Pkcs10Csr;
import com.yahoo.security.Pkcs10CsrBuilder;
import com.yahoo.security.SignatureAlgorithm;
import com.yahoo.security.X509CertificateBuilder;
import com.yahoo.test.ManualClock;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import javax.security.auth.x500.X500Principal;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * @author mortent
 * @author bjorncs
 */
public class LegacyAthenzIdentityProviderImplTest {

    @TempDir
    public File tempDir;

    public static final Duration certificateValidity = Duration.ofDays(30);

    private static final IdentityConfig IDENTITY_CONFIG =
            new IdentityConfig(new IdentityConfig.Builder()
                                       .service("tenantService")
                                       .domain("tenantDomain")
                                       .nodeIdentityName("vespa.tenant")
                                       .configserverIdentityName("vespa.configserver")
                                       .loadBalancerAddress("cfg")
                                       .ztsUrl("https:localhost:4443/zts/v1")
                                       .athenzDnsSuffix("dev-us-north-1.vespa.cloud"));

    private final KeyPair caKeypair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
    private Path trustStoreFile;
    private X509Certificate caCertificate;

    @BeforeEach
    public void createTrustStoreFile() throws IOException {
        caCertificate = X509CertificateBuilder
                .fromKeypair(
                        caKeypair,
                        new X500Principal("CN=mydummyca"),
                        Instant.EPOCH,
                        Instant.EPOCH.plus(10000, ChronoUnit.DAYS),
                        SignatureAlgorithm.SHA256_WITH_ECDSA,
                        BigInteger.ONE)
                .build();
        trustStoreFile = File.createTempFile("junit", null, tempDir).toPath();
        KeyStoreUtils.writeKeyStoreToFile(
                KeyStoreBuilder.withType(KeyStoreType.JKS)
                        .withKeyEntry("default", caKeypair.getPrivate(), caCertificate)
                        .build(),
                trustStoreFile);
    }

    @Test
    void component_creation_fails_when_credentials_not_found() {
        assertThrows(AthenzIdentityProviderException.class, () -> {
            AthenzCredentialsService credentialService = mock(AthenzCredentialsService.class);
            when(credentialService.registerInstance())
                    .thenThrow(new RuntimeException("athenz unavailable"));

            new LegacyAthenzIdentityProviderImpl(IDENTITY_CONFIG, mock(Metric.class), trustStoreFile, credentialService, mock(ScheduledExecutorService.class), new ManualClock(Instant.EPOCH));
        });
    }

    @Test
    void metrics_updated_on_refresh() {
        ManualClock clock = new ManualClock(Instant.EPOCH);
        Metric metric = mock(Metric.class);

        AthenzCredentialsService athenzCredentialsService = mock(AthenzCredentialsService.class);

        KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
        X509Certificate certificate = getCertificate(keyPair, getExpirationSupplier(clock));

        when(athenzCredentialsService.registerInstance())
                .thenReturn(new AthenzCredentials(certificate, keyPair, null));

        when(athenzCredentialsService.updateCredentials(any(), any()))
                .thenThrow(new RuntimeException("#1"))
                .thenThrow(new RuntimeException("#2"))
                .thenReturn(new AthenzCredentials(certificate, keyPair, null));

        LegacyAthenzIdentityProviderImpl identityProvider =
                new LegacyAthenzIdentityProviderImpl(IDENTITY_CONFIG, metric, trustStoreFile, athenzCredentialsService, mock(ScheduledExecutorService.class), clock);

        identityProvider.reportMetrics();
        verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.getSeconds()), any());

        // Advance 1 day, refresh fails, cert is 1 day old
        clock.advance(Duration.ofDays(1));
        identityProvider.refreshCertificate();
        identityProvider.reportMetrics();
        verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.minus(Duration.ofDays(1)).getSeconds()), any());

        // Advance 1 more day, refresh fails, cert is 2 days old
        clock.advance(Duration.ofDays(1));
        identityProvider.refreshCertificate();
        identityProvider.reportMetrics();
        verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.minus(Duration.ofDays(2)).getSeconds()), any());

        // Advance 1 more day, refresh succeds, cert is new
        clock.advance(Duration.ofDays(1));
        identityProvider.refreshCertificate();
        identityProvider.reportMetrics();
        verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.getSeconds()), any());

    }

    private Supplier<Date> getExpirationSupplier(ManualClock clock) {
        return () -> new Date(clock.instant().plus(certificateValidity).toEpochMilli());
    }

    private X509Certificate getCertificate(KeyPair keyPair, Supplier<Date> expiry) {
        Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(new X500Principal("CN=dummy"), keyPair, SignatureAlgorithm.SHA256_WITH_ECDSA)
                .build();
        return X509CertificateBuilder
                .fromCsr(csr,
                         caCertificate.getSubjectX500Principal(),
                         Instant.EPOCH,
                         expiry.get().toInstant(),
                         caKeypair.getPrivate(),
                         SignatureAlgorithm.SHA256_WITH_ECDSA,
                         BigInteger.ONE)
                .build();
    }

}