Skip to content

Commit b199560

Browse files
authored
Add support for SDS (#6597)
Motivation: #6610 should be reviewed/merged before this PR This changeset is part of an ongoing effort to support per-request TLS configurations by making xDS TLS resources first‑class. It adds minimal `Secret` (SDS) support so TLS certificates and validation contexts can be resolved (static or dynamic) and observed, including file-backed updates. Modifications: - Added `Secret` (SDS) resource parsing and registered the `Secret` type in discovery (`XdsType`, parser registry, and SOTW stubs). - Introduced TLS/transport socket snapshots and streaming resolution for secrets and TLS config. - Wired bootstrap static secrets into the subscription context. - Integrated file-backed `DataSource` watching via `DirectoryWatchService` and `PathWatcher`. - Extended cluster snapshots to include default `transport_socket` and `transport_socket_matches`. - New classes grouped by role: - Secrets/resources: - `BootstrapSecrets`: stores static bootstrap `Secret` resources for name lookup. - `SecretResourceParser`: parses `Secret` resources for the xDS pipeline. - `SecretXdsResource`: xDS resource wrapper for `Secret`s. - `SecretStream`: resolves `Secret`s via ADS/SDS or bootstrap lookup. - TLS snapshots: - `TlsCertificateSnapshot`: snapshot of TLS certificate plus parsed `TlsKeyPair`. - `CertificateValidationContextSnapshot`: snapshot of validation context plus parsed CA certs. - `TransportSocketSnapshot`: snapshot of transport socket with TLS material. - `TransportSocketMatchSnapshot`: snapshot for conditional transport socket matches. - TLS/data streams: - `TlsCertificateStream`: resolves TLS certificates from `Secret` + `DataSource`. - `CertificateValidationContextStream`: resolves validation contexts from `Secret` + `DataSource`. - `TransportSocketStream`: resolves transport socket TLS settings into snapshots. - `DataSourceStream`: resolves inline/env/file `DataSource` bytes and watches files. - Stream utilities: - `CombineLatest2Stream`: combines two snapshot streams and emits latest pairs. - `CombineLatest3Stream`: combines three snapshot streams and emits latest triples. Result: - Users can configure TLS via static or SDS-provided secrets and observe updated transport socket snapshots; file-backed cert/CA updates are propagated to snapshots.
1 parent c3d7df6 commit b199560

33 files changed

+4475
-74
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright 2026 LY Corporation
3+
*
4+
* LY Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.linecorp.armeria.xds.it;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.awaitility.Awaitility.await;
21+
22+
import java.util.concurrent.atomic.AtomicReference;
23+
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.extension.RegisterExtension;
26+
27+
import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension;
28+
import com.linecorp.armeria.xds.CertificateValidationContextSnapshot;
29+
import com.linecorp.armeria.xds.ListenerRoot;
30+
import com.linecorp.armeria.xds.ListenerSnapshot;
31+
import com.linecorp.armeria.xds.TlsCertificateSnapshot;
32+
import com.linecorp.armeria.xds.TransportSocketSnapshot;
33+
import com.linecorp.armeria.xds.XdsBootstrap;
34+
35+
import io.envoyproxy.envoy.config.bootstrap.v3.Bootstrap;
36+
37+
class BootstrapSecretsTest {
38+
39+
@RegisterExtension
40+
static final SelfSignedCertificateExtension certificate1 = new SelfSignedCertificateExtension();
41+
@RegisterExtension
42+
static final SelfSignedCertificateExtension certificate2 = new SelfSignedCertificateExtension();
43+
44+
//language=YAML
45+
private static final String staticBootstrap =
46+
"""
47+
static_resources:
48+
clusters:
49+
- name: my-cluster
50+
type: STATIC
51+
load_assignment:
52+
cluster_name: my-cluster
53+
endpoints:
54+
- lb_endpoints:
55+
- endpoint:
56+
address:
57+
socket_address:
58+
address: 127.0.0.1
59+
port_value: 8080
60+
transport_socket:
61+
name: envoy.transport_sockets.tls
62+
typed_config:
63+
"@type": type.googleapis.com/envoy.extensions.transport_sockets\
64+
.tls.v3.UpstreamTlsContext
65+
common_tls_context:
66+
tls_certificate_sds_secret_configs:
67+
- name: my-cert
68+
validation_context_sds_secret_config:
69+
name: my-validation
70+
- name: no-tls-cluster
71+
type: STATIC
72+
load_assignment:
73+
cluster_name: no-tls-cluster
74+
endpoints:
75+
- lb_endpoints:
76+
- endpoint:
77+
address:
78+
socket_address:
79+
address: 127.0.0.1
80+
port_value: 8080
81+
listeners:
82+
- name: my-listener
83+
api_listener:
84+
api_listener:
85+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager\
86+
.v3.HttpConnectionManager
87+
stat_prefix: http
88+
route_config:
89+
name: local_route
90+
virtual_hosts:
91+
- name: local_service1
92+
domains: [ "*" ]
93+
routes:
94+
- match:
95+
prefix: /
96+
route:
97+
cluster: my-cluster
98+
- match:
99+
prefix: /no-tls
100+
route:
101+
cluster: no-tls-cluster
102+
http_filters:
103+
- name: envoy.filters.http.router
104+
typed_config:
105+
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
106+
secrets:
107+
- name: my-cert
108+
tls_certificate:
109+
private_key:
110+
filename: %s
111+
certificate_chain:
112+
filename: %s
113+
- name: my-validation
114+
validation_context:
115+
trusted_ca:
116+
filename: %s
117+
""";
118+
119+
@Test
120+
void staticSecretLoaded() throws Exception {
121+
final String formatted = staticBootstrap.formatted(certificate1.privateKeyFile().toPath().toString(),
122+
certificate1.certificateFile().toPath().toString(),
123+
certificate2.certificateFile().toPath().toString());
124+
final Bootstrap bootstrap = XdsResourceReader.fromYaml(formatted);
125+
try (XdsBootstrap xdsBootstrap = XdsBootstrap.of(bootstrap)) {
126+
final ListenerRoot listenerRoot = xdsBootstrap.listenerRoot("my-listener");
127+
final AtomicReference<ListenerSnapshot> snapshotRef = new AtomicReference<>();
128+
listenerRoot.addSnapshotWatcher((snapshot, t) -> {
129+
if (snapshot != null) {
130+
snapshotRef.set(snapshot);
131+
}
132+
});
133+
134+
await().untilAsserted(() -> assertThat(snapshotRef.get()).isNotNull());
135+
final ListenerSnapshot listenerSnapshot = snapshotRef.get();
136+
final TransportSocketSnapshot
137+
socket1 = listenerSnapshot.routeSnapshot().virtualHostSnapshots().get(0)
138+
.routeEntries().get(0).clusterSnapshot().transportSocket();
139+
final TlsCertificateSnapshot certSnapshot = socket1.tlsCertificate();
140+
assertThat(certSnapshot).isNotNull();
141+
assertThat(certSnapshot.tlsKeyPair()).isEqualTo(certificate1.tlsKeyPair());
142+
final CertificateValidationContextSnapshot validationContext = socket1.validationContext();
143+
assertThat(validationContext).isNotNull();
144+
assertThat(validationContext.trustedCa()).containsExactly(certificate2.certificate());
145+
146+
final TransportSocketSnapshot
147+
socket2 = listenerSnapshot.routeSnapshot().virtualHostSnapshots().get(0)
148+
.routeEntries().get(1).clusterSnapshot().transportSocket();
149+
assertThat(socket2.validationContext()).isNull();
150+
assertThat(socket2.tlsCertificate()).isNull();
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)