Skip to content

Commit 3e48ca2

Browse files
authored
Add support for OKE workload identity authentication in the cloud service
1 parent aed78fc commit 3e48ca2

15 files changed

+739
-269
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ target
22
*.versionsBackup
33
*.diff
44
Fortify*
5+
logging.properties

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
77
### Added
88
- Cloud only: added new OCI regions (TYO, AHU, DAC, DOH, IZQ)
99
- Cloud only: added use of ETag in AddReplicaRequest and DropReplicaRequest
10+
- Cloud only: added OKE workload identity authentication support
11+
- SignatureProvider.createWithOkeWorkloadIdentity()
12+
- SignatureProvider.createWithOkeWorkloadIdentity(String serviceAccountToken, Logger logger)
13+
- SignatureProvider.createWithOkeWorkloadIdentity(File serviceAccountTokenFile, Logger logger)
1014

1115
### Fixed
1216
- Changed handle close to not use the Netty "quiet period" when shutting down

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,15 @@ public class Quickstart {
243243
private String endpoint;
244244
private String service;
245245
246-
/* required for Instance/Resource Principal auth */
246+
/* required for Instance/Resource Principal auth and OKE workload identity */
247247
private String compartment = null; // an OCID
248248
249249
/* alternative cloud authorization mechanisms */
250250
private final static boolean useUserPrincipal = true;
251251
private final static boolean useInstancePrincipal = false;
252252
private final static boolean useResourcePrincipal = false;
253253
private final static boolean useSessionToken = false;
254+
private final static boolean useOkeWorkloadIdentity = false;
254255
255256
private Quickstart(String[] args) {
256257
/*
@@ -350,6 +351,9 @@ public class Quickstart {
350351
} else if (useResourcePrincipal) {
351352
authProvider =
352353
SignatureProvider.createWithResourcePrincipal();
354+
} else if (useOkeWorkloadIdentity) {
355+
authProvider =
356+
SignatureProvider.createWithOkeWorkloadIdentity();
353357
} else {
354358
throw new IllegalArgumentException(
355359
"Authorization method is required");

driver/src/main/java/oracle/nosql/driver/httpclient/HttpClient.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ public class HttpClient {
111111
private final SslContext sslCtx;
112112
private final int handshakeTimeoutMs;
113113

114+
/* Enable endpoint identification by default if using SSL */
115+
private boolean enableEndpointIdentification = true;
116+
114117
private final Logger logger;
115118

116119
/*
@@ -282,6 +285,14 @@ SslContext getSslContext() {
282285
return sslCtx;
283286
}
284287

288+
public boolean isEndpointIdentificationEnabled() {
289+
return enableEndpointIdentification;
290+
}
291+
292+
public void disableEndpointIdentification() {
293+
this.enableEndpointIdentification = false;
294+
}
295+
285296
public int getPort() {
286297
return port;
287298
}

driver/src/main/java/oracle/nosql/driver/httpclient/HttpClientChannelPoolHandler.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,13 @@ public void channelCreated(Channel ch) {
7272
/* Enable hostname verification */
7373
final SslHandler sslHandler = client.getSslContext().newHandler(
7474
ch.alloc(), client.getHost(), client.getPort());
75-
final SSLEngine sslEngine = sslHandler.engine();
76-
final SSLParameters sslParameters = sslEngine.getSSLParameters();
77-
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
78-
sslEngine.setSSLParameters(sslParameters);
75+
76+
if (client.isEndpointIdentificationEnabled()) {
77+
final SSLEngine sslEngine = sslHandler.engine();
78+
final SSLParameters sslParameters = sslEngine.getSSLParameters();
79+
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
80+
sslEngine.setSSLParameters(sslParameters);
81+
}
7982
sslHandler.setHandshakeTimeoutMillis(client.getHandshakeTimeoutMs());
8083

8184
p.addLast(sslHandler);

driver/src/main/java/oracle/nosql/driver/iam/FederationRequestHelper.java

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ static String getSecurityToken(HttpClient client,
6969
response.getOutput()));
7070
}
7171
logTrace(logger, "Federation response " + response.getOutput());
72-
return parseResponse(response.getOutput());
72+
return parseTokenResponse(response.getOutput());
7373
}
7474

7575
/*
@@ -168,30 +168,4 @@ private static String keyId(String tenantId, X509CertificateKeyPair pair) {
168168
return String.format("%s/fed-x509-sha256/%s",
169169
tenantId, Utils.getFingerPrint(pair));
170170
}
171-
172-
/*
173-
* Response:
174-
* { "token": "...."}
175-
*/
176-
private static String parseResponse(String response) {
177-
try {
178-
JsonParser parser = createParser(response);
179-
if (parser.getCurrentToken() == null) {
180-
parser.nextToken();
181-
}
182-
while (parser.getCurrentToken() != null) {
183-
String field = findField(response, parser, "token");
184-
if (field != null) {
185-
parser.nextToken();
186-
return parser.getText();
187-
}
188-
}
189-
throw new IllegalStateException(
190-
"Unable to find security token in " + response);
191-
} catch (IOException ioe) {
192-
throw new IllegalStateException(
193-
"Error parsing security token " + response +
194-
" " + ioe.getMessage());
195-
}
196-
}
197171
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*-
2+
* Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* Licensed under the Universal Permissive License v 1.0 as shown at
5+
* https://oss.oracle.com/licenses/upl/
6+
*/
7+
8+
package oracle.nosql.driver.iam;
9+
10+
import static oracle.nosql.driver.iam.Utils.*;
11+
import static oracle.nosql.driver.util.HttpConstants.APPLICATION_JSON;
12+
import static oracle.nosql.driver.util.HttpConstants.AUTHORIZATION;
13+
import static oracle.nosql.driver.util.HttpConstants.CONTENT_TYPE;
14+
15+
import java.io.IOException;
16+
import java.util.logging.Logger;
17+
18+
import oracle.nosql.driver.httpclient.HttpClient;
19+
import oracle.nosql.driver.util.HttpRequestUtil;
20+
21+
import com.fasterxml.jackson.core.JsonFactory;
22+
import com.fasterxml.jackson.core.JsonParser;
23+
import io.netty.handler.codec.http.DefaultHttpHeaders;
24+
import io.netty.handler.codec.http.HttpHeaders;
25+
26+
/**
27+
* @hidden
28+
* Internal use only
29+
* <p>
30+
* Helper class to fetch instance metadata from metadata service URL.
31+
*/
32+
class InstanceMetadataHelper {
33+
private static final JsonFactory factory = new JsonFactory();
34+
35+
/* Instance metadata service base URL */
36+
private static final String METADATA_SERVICE_BASE_URL =
37+
"http://169.254.169.254/opc/v2/";
38+
private static final String FALLBACK_METADATA_SERVICE_URL =
39+
"http://169.254.169.254/opc/v1/";
40+
41+
/* The authorization header need to send to metadata service since V2 */
42+
static final String AUTHORIZATION_HEADER_VALUE = "Bearer Oracle";
43+
private static final String METADATA_SERVICE_HOST =
44+
"169.254.169.254";
45+
46+
static String getInstanceMetadaURL(String baseMetadataURL) {
47+
return baseMetadataURL + "instance/";
48+
}
49+
50+
/**
51+
* Fetch the instance metadata.
52+
*
53+
* @param timeout request timeout
54+
* @param logger logger
55+
*/
56+
static InstanceMetadata fetchMetadata(int timeout, Logger logger) {
57+
String baseMetadataURL = METADATA_SERVICE_BASE_URL;
58+
String instanceMDURL = getInstanceMetadaURL(baseMetadataURL);
59+
logTrace(logger, "Fetch instance metadata using " + instanceMDURL);
60+
HttpClient client = null;
61+
try {
62+
client = HttpClient.createMinimalClient(METADATA_SERVICE_HOST,
63+
80,
64+
null,
65+
0,
66+
"InstanceMDClient",
67+
logger);
68+
69+
HttpRequestUtil.HttpResponse response = HttpRequestUtil.doGetRequest
70+
(client, instanceMDURL, headers(), timeout, logger);
71+
72+
int status = response.getStatusCode();
73+
if (status == 404) {
74+
logTrace(logger, "Falling back to v1 metadata URL, " +
75+
"resource not found from v2");
76+
baseMetadataURL = FALLBACK_METADATA_SERVICE_URL;
77+
instanceMDURL = getInstanceMetadaURL(baseMetadataURL);
78+
response = HttpRequestUtil.doGetRequest
79+
(client, instanceMDURL, headers(), timeout, logger);
80+
if (response.getStatusCode() != 200) {
81+
throw new IllegalStateException(
82+
String.format("Unable to get federation URL from" +
83+
"instance metadata " + METADATA_SERVICE_BASE_URL +
84+
" or fallback to " + FALLBACK_METADATA_SERVICE_URL +
85+
", status code: %d, output: %s",
86+
response.getOutput()));
87+
}
88+
} else if (status != 200) {
89+
throw new IllegalStateException(
90+
String.format("Unable to get federation URL from" +
91+
"instance metadata " + METADATA_SERVICE_BASE_URL +
92+
", status code: %d, output: %s",
93+
response.getStatusCode(),
94+
response.getOutput()));
95+
}
96+
97+
logTrace(logger, "Instance metadata " + response.getOutput());
98+
String insRegion = findRegion(response.getOutput());
99+
logTrace(logger, "Instance region " + insRegion);
100+
return new InstanceMetadata(insRegion, baseMetadataURL);
101+
} finally {
102+
if (client != null) {
103+
client.shutdown();
104+
}
105+
}
106+
}
107+
108+
private static HttpHeaders headers() {
109+
return new DefaultHttpHeaders()
110+
.set(CONTENT_TYPE, APPLICATION_JSON)
111+
.set(AUTHORIZATION, AUTHORIZATION_HEADER_VALUE);
112+
}
113+
114+
private static String findRegion(String response) {
115+
try {
116+
JsonParser parser = factory.createParser(response);
117+
if (parser.getCurrentToken() == null) {
118+
parser.nextToken();
119+
}
120+
while (parser.getCurrentToken() != null) {
121+
String field = findField(
122+
response, parser, "canonicalRegionName");
123+
if (field != null) {
124+
parser.nextToken();
125+
return parser.getText();
126+
}
127+
}
128+
throw new IllegalStateException(
129+
"Unable to find region in instance metadata " + response);
130+
} catch (IOException ioe) {
131+
throw new IllegalStateException(
132+
"Error parsing instance metadata in response " +
133+
response+ " " + ioe.getMessage());
134+
}
135+
}
136+
137+
static class InstanceMetadata {
138+
private String region;
139+
private String baseMetadataURL;
140+
141+
InstanceMetadata(String region, String baseMetadataURL) {
142+
this.region = region;
143+
this.baseMetadataURL = baseMetadataURL;
144+
}
145+
146+
String getRegion() {
147+
return region;
148+
}
149+
150+
String getBaseURL() {
151+
return baseMetadataURL;
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)