diff --git a/driver/src/main/java/oracle/nosql/driver/iam/AbstractTokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/AbstractTokenSupplier.java new file mode 100644 index 00000000..5090992f --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/AbstractTokenSupplier.java @@ -0,0 +1,261 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.Utils.isNotBlank; +import static oracle.nosql.driver.iam.Utils.logTrace; +import static oracle.nosql.driver.iam.Utils.parseTokenResponse; +import static oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityToken; +import static oracle.nosql.driver.util.HttpRequestUtil.HttpResponse; + +import javax.net.ssl.SSLException; +import java.net.URI; +import java.util.Objects; +import java.util.logging.Logger; + +import oracle.nosql.driver.NoSQLHandleConfig; +import oracle.nosql.driver.SecurityInfoNotReadyException; +import oracle.nosql.driver.httpclient.HttpClient; +import oracle.nosql.driver.util.HttpRequestUtil; + +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; + +abstract class AbstractTokenSupplier + implements TokenSupplier{ + + private static final int DEFAULT_TIMEOUT_MS = 5_000; + protected static final int timeoutMs = DEFAULT_TIMEOUT_MS; + protected final SessionKeyPairSupplier sessionKeyPairSupplier; + protected HttpClient resourcePrincipalTokenClient; + protected HttpClient federationClient; + protected URI resourcePrincipalTokenURI; + protected final URI federationURI; + private volatile SecurityToken securityToken; + private long minTokenLifetime; + protected String keyId; + protected PrivateKeyProvider privateKeyProvider; + private final Logger logger; + + public AbstractTokenSupplier( + String resourcePrincipalTokenEndpoint, + String federationEndpoint, + String resourcePrincipalTokenPath, + SessionKeyPairSupplier sessionKeySupplier, + Logger logger + ){ + this ( + resourcePrincipalTokenEndpoint + resourcePrincipalTokenPath, + federationEndpoint, + sessionKeySupplier, + logger + ); + } + + public AbstractTokenSupplier( + String resourcePrincipalTokenUrl, + String federationEndpoint, + SessionKeyPairSupplier sessionKeySupplier, + Logger logger + ){ + Objects.requireNonNull(resourcePrincipalTokenUrl, + "resourcePrincipalTokenEndpoint cannot be null"); + Objects.requireNonNull(federationEndpoint, + "federationEndpoint"); + Objects.requireNonNull(sessionKeySupplier, + "sessionKeySupplier cannot be null"); + + this.sessionKeyPairSupplier = sessionKeySupplier; + this.logger = logger; + this.resourcePrincipalTokenURI = URI.create(resourcePrincipalTokenUrl); + this.federationURI = + URI.create(federationEndpoint + "/v1/resourcePrincipalSessionToken"); + this.resourcePrincipalTokenClient = + buildHttpClient(resourcePrincipalTokenURI, + "resourcePrincipalTokenClient", logger); + this.federationClient = + buildHttpClient(federationURI, "federationClient",logger); + } + + /** + * Return the security token of resource principal. + */ + @Override + public String getSecurityToken() { + return refreshAndGetSecurityToken(); + } + + @Override + public void prepare(NoSQLHandleConfig config) { + } + + /** + * Cleans up the resources held by the token supplier + */ + @Override + public synchronized void close() { + if (resourcePrincipalTokenClient != null) { + resourcePrincipalTokenClient.shutdown(); + } + if (federationClient != null) { + federationClient.shutdown(); + } + } + + /** + * Set expected minimal token lifetime. + */ + @Override + public void setMinTokenLifetime(long lifetimeMS) { + this.minTokenLifetime = lifetimeMS; + } + + /** + * Return the specific claim in security token by given key. + */ + @Override + public String getStringClaim(String key) { + if (securityToken == null) { + refreshAndGetSecurityToken(); + } + return securityToken.getStringClaim(key); + } + + protected static HttpClient buildHttpClient(URI endpoint, + String clientName, + Logger logger) { + String scheme = endpoint.getScheme(); + if (scheme == null) { + throw new IllegalArgumentException( + "Unable to find URL scheme, invalid URL " + + endpoint.toString()); + } + if (scheme.equalsIgnoreCase("http")) { + return HttpClient.createMinimalClient(endpoint.getHost(), endpoint.getPort(), + null, 0, clientName, logger); + } + + SslContext sslCtx; + try { + sslCtx = SslContextBuilder.forClient().build(); + } catch (SSLException se) { + throw new IllegalStateException( + "Unable to build SSL context for http client", se); + } + + return HttpClient.createMinimalClient(endpoint.getHost(), 443, + sslCtx, 0, + clientName, logger); + } + + protected synchronized String refreshAndGetSecurityToken() { + logTrace(logger, "Refreshing session keys"); + sessionKeyPairSupplier.refreshKeys(); + + try { + logTrace(logger, "Getting security token from Federation Server."); + SecurityTokenSupplier.SecurityToken token = getSecurityTokenFromServer(); + token.validate(minTokenLifetime, logger); + securityToken = token; + return securityToken.getSecurityToken(); + } catch (Exception e) { + close(); // release the resources held by http clients + throw new SecurityInfoNotReadyException(e.getMessage(), e); + } + } + + /** + * Gets a security token from the federation server + * + * @return the security token, which is basically a JWT token string + */ + protected abstract SecurityToken getSecurityTokenFromServer(); + + protected HttpResponse getResourcePrincipalTokenResponse( + String securityContext) { + + HttpHeaders resourcePrincipalTokenHeader = FederationRequestHelper.setHeaders( + resourcePrincipalTokenURI, + keyId, + privateKeyProvider.getKey(), + logger); + + if (isNotBlank(securityContext)) { + resourcePrincipalTokenHeader.set("security-context", securityContext); + } + + // Fetch Resource Principal Token from the Resource's control plane + HttpResponse resourcePrincipalTokenResponse = + HttpRequestUtil.doGetRequest( + resourcePrincipalTokenClient, + resourcePrincipalTokenURI.toString(), + resourcePrincipalTokenHeader, + timeoutMs, + logger); + + int status = resourcePrincipalTokenResponse.getStatusCode(); + if(status != 200) { + throw new IllegalStateException( + String.format("Unable to fetch Resource Principal Token " + + "from resource control plane endpoint - %s" + + ", status code: %d, output: %s", + resourcePrincipalTokenURI.getHost(), + resourcePrincipalTokenResponse.getStatusCode(), + resourcePrincipalTokenResponse.getOutput())); + } + + return resourcePrincipalTokenResponse; + } + + protected SecurityToken getResourcePrincipalSessionToken( + String resourcePrincipalToken, + String servicePrincipalSessionToken, + String publicKey + ) { + String payload = + FederationRequestHelper.getResourcePrincipalSessionTokenRequestBody( + resourcePrincipalToken, + servicePrincipalSessionToken, + publicKey); + + byte[] payloadBytes = payload.getBytes(); + + // Get Resource principal session token from Identity data plane + HttpRequestUtil.HttpResponse resourcePrincipalSessionTokenResponse = + HttpRequestUtil.doPostRequest( + federationClient, + federationURI.toString(), + FederationRequestHelper.setHeaders( + federationURI, + payloadBytes, + keyId, + privateKeyProvider.getKey(), + logger), + payloadBytes, + timeoutMs, + logger); + + int status = resourcePrincipalSessionTokenResponse.getStatusCode(); + if(status != 200) { + throw new IllegalStateException( + String.format("Unable to fetch Resource Principal Session " + + "Token from federation endpoint - %s" + + ", status code: %d, output: %s", + federationURI.getHost(), + resourcePrincipalSessionTokenResponse.getStatusCode(), + resourcePrincipalSessionTokenResponse.getOutput())); + } + + String resourcePrincipalSessionToken = parseTokenResponse( + resourcePrincipalSessionTokenResponse.getOutput()); + + return new SecurityToken( + resourcePrincipalSessionToken, sessionKeyPairSupplier); + } +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/BaseProviderBuilder.java b/driver/src/main/java/oracle/nosql/driver/iam/BaseProviderBuilder.java new file mode 100644 index 00000000..dd26f4dc --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/BaseProviderBuilder.java @@ -0,0 +1,271 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.Utils.getIAMURL; +import static oracle.nosql.driver.util.HttpConstants.AUTHORIZATION; +import static oracle.nosql.driver.iam.SessionKeyPairSupplier.JDKKeyPairSupplier; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import oracle.nosql.driver.Region; + +class BaseProviderBuilder> { + protected Logger logger; + + /** The default value for HTTP request timeouts in milliseconds */ + private static final int DEFAULT_TIMEOUT_MS = 5_000; + + /** The default purpose value in federation requests against IAM */ + private static final String DEFAULT_PURPOSE = "DEFAULT"; + + /** IAM federation endpoint, or null if detecting from instance metadata. */ + protected String federationEndpoint; + + /** The leaf certificate, or null if detecting from instance metadata. */ + protected CertificateSupplier leafCertificateSupplier; + + /** Intermediate certificates or null if detecting from instance metadata. */ + protected Set intermediateCertificateSuppliers; + + /** Instance metadata */ + private InstanceMetadataHelper.InstanceMetadata instanceMetadata; + + /** Session key pair supplier. */ + protected SessionKeyPairSupplier sessionKeySupplier = new JDKKeyPairSupplier(); + + /** HTTP client which will supply security tokens */ + protected TokenSupplier tokenSupplier; + + /** Tenant id, or null if detecting from instance metadata. */ + protected String tenantId; + + /** The custom timeout for each HTTP request. */ + protected int timeout = DEFAULT_TIMEOUT_MS; + + protected String purpose = DEFAULT_PURPOSE; + + /** Detected region. */ + protected Region region; + + public String getFederationEndpoint() { + return federationEndpoint; + } + + /** + * @hidden + * @param federationEndpoint the endpoint + * @return this + */ + @SuppressWarnings("unchecked") + public T setFederationEndpoint(String federationEndpoint) { + this.federationEndpoint = federationEndpoint; + return (T) this; + } + + public CertificateSupplier getLeafCertificateSupplier() { + return leafCertificateSupplier; + } + + /** + * @hidden + * @param supplier the supplier + * @return this + */ + @SuppressWarnings("unchecked") + public T setLeafCertificateSupplier(CertificateSupplier supplier) { + this.leafCertificateSupplier = supplier; + return (T) this; + } + + public String getTenantId() { + return tenantId; + } + + /** + * @hidden + * @param tenantId the tenant + * @return this + */ + @SuppressWarnings("unchecked") + public T setTenantId(String tenantId) { + this.tenantId = tenantId; + return (T) this; + } + + public String getPurpose() { + return purpose; + } + + /** + * @hidden + * @param purpose the purpose + * @return this + */ + @SuppressWarnings("unchecked") + public T setPurpose(String purpose) { + this.purpose = purpose; + return (T) this; + } + + public SessionKeyPairSupplier getSesssionKeyPairSupplier() { + return sessionKeySupplier; + } + + /** + * @hidden + * @param sessSupplier the supplier + * @return this + */ + @SuppressWarnings("unchecked") + public T setSessionKeyPairSupplier(SessionKeyPairSupplier sessSupplier) { + this.sessionKeySupplier = sessSupplier; + return (T) this; + } + + public int getTimeout() { + return timeout; + } + + /** + * @hidden + * @param timeout the timeout + * @return this + */ + @SuppressWarnings("unchecked") + public T setTimeout(int timeout) { + this.timeout = timeout; + return (T) this; + } + + public Logger getLogger() { + return logger; + } + + /** + * @hidden + * @param logger the logger + * @return this + */ + @SuppressWarnings("unchecked") + public T setLogger(Logger logger) { + this.logger = logger; + return (T) this; + } + + public Set getIntermediateCertificateSuppliers() { + return intermediateCertificateSuppliers; + } + + /** + * @hidden + * @param suppliers suppliers + * @return this + */ + @SuppressWarnings("unchecked") + public T setIntermediateCertificateSuppliers( + Set suppliers) { + this.intermediateCertificateSuppliers = suppliers; + return (T) this; + } + + /** + * Returns the region if set + * @return the region or null if not set + */ + public Region getRegion() { + return region; + } + + /** + * Sets a region + * @param r the region + * @return this + */ + @SuppressWarnings("unchecked") + public T setRegion(Region r) { + this.region = r; + return (T) this; + } + + /* + * Auto detects the endpoint and region that should be used + * when talking to IAM, if no endpoint has been configured already. + */ + protected String autoDetectEndpointUsingMetadataUrl() { + final String insRegion = getInstanceMetadata().getRegion(); + if (region == null) { + region = Region.fromRegionId(insRegion); + } + if (federationEndpoint == null) { + federationEndpoint = getIAMURL(insRegion); + if (federationEndpoint == null) { + throw new IllegalArgumentException( + "Unable to find IAM URL for unregistered region " + + region + ", specify the IAM URL instead"); + } + } + return federationEndpoint; + } + + /* + * Auto detects and configures the certificates needed + * using Instance metadata. + */ + protected void autoDetectCertificatesUsingMetadataUrl() { + try { + if (leafCertificateSupplier == null) { + leafCertificateSupplier = new CertificateSupplier.DefaultCertificateSupplier( + getURLDetails(getInstanceMetadata().getBaseURL() + + "identity/cert.pem"), + getURLDetails(getInstanceMetadata().getBaseURL() + + "identity/key.pem"), + (char[]) null); + } + + if (tenantId == null) { + tenantId = Utils.getTenantId(leafCertificateSupplier + .getCertificateAndKeyPair().getCertificate()); + } + + if (intermediateCertificateSuppliers == null) { + intermediateCertificateSuppliers = new HashSet<>(); + + intermediateCertificateSuppliers.add( + new CertificateSupplier.DefaultCertificateSupplier( + getURLDetails(getInstanceMetadata().getBaseURL() + + "identity/intermediate.pem"), + null, + (char[]) null)); + } + } catch (MalformedURLException ex) { + throw new IllegalArgumentException( + "The instance metadata service url is invalid.", ex); + } + } + + private CertificateSupplier.URLResourceDetails getURLDetails(String url) + throws MalformedURLException { + + return new CertificateSupplier.URLResourceDetails(new URL(url)).addHeader( + AUTHORIZATION, + InstanceMetadataHelper.AUTHORIZATION_HEADER_VALUE); + } + + private InstanceMetadataHelper.InstanceMetadata getInstanceMetadata() { + if (instanceMetadata == null) { + instanceMetadata = InstanceMetadataHelper + .fetchMetadata(timeout, logger); + } + return instanceMetadata; + } +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/FederationRequestHelper.java b/driver/src/main/java/oracle/nosql/driver/iam/FederationRequestHelper.java index 51ab9219..187b7e8b 100644 --- a/driver/src/main/java/oracle/nosql/driver/iam/FederationRequestHelper.java +++ b/driver/src/main/java/oracle/nosql/driver/iam/FederationRequestHelper.java @@ -13,10 +13,10 @@ import java.io.IOException; import java.io.StringWriter; import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.logging.Logger; @@ -36,8 +36,10 @@ */ class FederationRequestHelper { /* signing headers used to obtain security token */ - private static final String SIGNING_HEADERS = - "date (request-target) content-length content-type x-content-sha256"; + private static final String SIGNING_HEADERS_WITH_PAYLOAD = + "date (request-target) content-length content-type x-content-sha256"; + private static final String SIGNING_HEADERS_WITHOUT_PAYLOAD = + "date (request-target)"; private static final String APP_JSON = "application/json"; private static final String DEFAULT_FINGERPRINT = "SHA256"; @@ -48,15 +50,21 @@ static String getSecurityToken(HttpClient client, X509CertificateKeyPair pair, String body, Logger logger) { - CharBuffer charBuf = CharBuffer.wrap(body); - ByteBuffer buf = StandardCharsets.UTF_8.encode(charBuf); - byte[] payloadByte = new byte[buf.remaining()]; - buf.get(payloadByte); + byte[] payloadByte = Utils.stringToUtf8Bytes(body); HttpResponse response = HttpRequestUtil.doPostRequest( - client, endpoint.toString(), - headers(tenantId, endpoint, payloadByte, pair, logger), - payloadByte, timeoutMs, logger); + client, + endpoint.toString(), + setHeaders( + endpoint, + payloadByte, + keyId(tenantId, pair), + pair.getKey(), + logger + ), + payloadByte, + timeoutMs, + logger); int responseCode = response.getStatusCode(); if (responseCode > 299) { @@ -85,11 +93,11 @@ static String getSecurityToken(HttpClient client, * "fingerprintAlgorithm", "SHA256" * } */ - static String getFederationRequestBody(String publicKey, - String certificate, - Set interCerts, - String purpose) { - + static String getInstancePrincipalSessionTokenRequestBody( + String publicKey, + String certificate, + Set interCerts, + String purpose) { try { StringWriter sw = new StringWriter(); try (JsonGenerator gen = createGenerator(sw)) { @@ -116,51 +124,108 @@ static String getFederationRequestBody(String publicKey, } } - private static HttpHeaders headers(String tenantId, - URI endpoint, - byte[] body, - X509CertificateKeyPair pair, - Logger logger) { + /* + * Request body: + * { + * "resourcePrincipalToken": "....", + * "servicePrincipalSessionToken": "....", + * "sessionPublicKey": "publicKey" + * } + */ + static String getResourcePrincipalSessionTokenRequestBody( + String resourcePrincipalToken, + String servicePrincipalSessionToken, + String sessionPublicKey){ + Map jsonMap = new HashMap<>(); + jsonMap.put("resourcePrincipalToken", resourcePrincipalToken); + jsonMap.put("servicePrincipalSessionToken", servicePrincipalSessionToken); + jsonMap.put("sessionPublicKey", sessionPublicKey); + return convertMapToJson(jsonMap); + } - String date = createFormatter().format(new Date()); + static HttpHeaders setHeaders(URI uri, + String keyId, + PrivateKey privateKey, + Logger logger) { + final String date = createFormatter().format(new Date()); + StringBuilder sign = new StringBuilder(); + sign.append(DATE).append(HEADER_DELIMITER) + .append(date).append("\n") + .append(REQUEST_TARGET).append(HEADER_DELIMITER) + .append("get ").append(uri.getPath()); + + logTrace(logger, "Resource Principal Token request" + + " signing content " + sign); + + String signature; + try { + signature = sign(sign.toString(), privateKey); + } catch (Exception e) { + return null; + } + + String authHeader = String.format( + SIGNATURE_HEADER_FORMAT, + SIGNING_HEADERS_WITHOUT_PAYLOAD, + keyId, + RSA, + signature, + SINGATURE_VERSION); + + + logTrace(logger, "Resource Principal Token request" + + " authorization header " + authHeader); + HttpHeaders headers = new DefaultHttpHeaders(); + return headers + .set(DATE, date) + .set(AUTHORIZATION.toLowerCase(), authHeader); + } + + static HttpHeaders setHeaders(URI uri, + byte[] body, + String keyId, + PrivateKey privateKey, + Logger logger) { + final String date = createFormatter().format(new Date()); String bodySha = computeBodySHA256(body); StringBuilder sign = new StringBuilder(); sign.append(DATE).append(HEADER_DELIMITER) - .append(date).append("\n") - .append(REQUEST_TARGET).append(HEADER_DELIMITER) - .append("post ").append(endpoint.getPath()).append("\n") - .append(CONTENT_LENGTH.toLowerCase()).append(HEADER_DELIMITER) - .append(Integer.toString(body.length)).append("\n") - .append(CONTENT_TYPE.toLowerCase()).append(HEADER_DELIMITER) - .append(APP_JSON).append("\n") - .append(CONTENT_SHA).append(HEADER_DELIMITER) - .append(bodySha); - - logTrace(logger, "Federation request signing content " + - sign.toString()); + .append(date).append("\n") + .append(REQUEST_TARGET).append(HEADER_DELIMITER) + .append("post ").append(uri.getPath()).append("\n") + .append(CONTENT_LENGTH.toLowerCase()).append(HEADER_DELIMITER) + .append(body.length).append("\n") + .append(CONTENT_TYPE.toLowerCase()).append(HEADER_DELIMITER) + .append(APP_JSON).append("\n") + .append(CONTENT_SHA).append(HEADER_DELIMITER) + .append(bodySha); + + logTrace(logger, "Federation Request signing content " + + sign); String signature; try { - signature = sign(sign.toString(), pair.getKey()); + signature = sign(sign.toString(), privateKey); } catch (Exception e) { return null; } + String authHeader = String.format( - SIGNATURE_HEADER_FORMAT, - SIGNING_HEADERS, - keyId(tenantId, pair), - RSA, - signature, - SINGATURE_VERSION); + SIGNATURE_HEADER_FORMAT, + SIGNING_HEADERS_WITH_PAYLOAD, + keyId, + RSA, + signature, + SINGATURE_VERSION); logTrace(logger, "Federation request authorization header " + - authHeader); + authHeader); HttpHeaders headers = new DefaultHttpHeaders(); return headers - .set(CONTENT_TYPE.toLowerCase(), APP_JSON) - .set(CONTENT_SHA, bodySha) - .set(CONTENT_LENGTH.toLowerCase(), body.length) - .set(DATE, date) - .set(AUTHORIZATION.toLowerCase(), authHeader); + .set(DATE, date) + .set(AUTHORIZATION.toLowerCase(), authHeader) + .set(CONTENT_TYPE.toLowerCase(), APP_JSON) + .set(CONTENT_SHA, bodySha) + .set(CONTENT_LENGTH.toLowerCase(), body.length); } private static String keyId(String tenantId, X509CertificateKeyPair pair) { diff --git a/driver/src/main/java/oracle/nosql/driver/iam/FileSecurityTokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/FileSecurityTokenSupplier.java new file mode 100644 index 00000000..c43465ca --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/FileSecurityTokenSupplier.java @@ -0,0 +1,107 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.Utils.logTrace; +import static oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityToken; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.util.logging.Logger; + +import oracle.nosql.driver.NoSQLHandleConfig; +import oracle.nosql.driver.SecurityInfoNotReadyException; + +/** + * @hidden + * Internal use only + *

+ * The interface to supply security token for resource principal from file. + * Reference to the OCI SDK for Java + * com.oracle.bmc.auth.internal.FileBasedResourcePrincipalFederationClient + */ +class FileSecurityTokenSupplier + implements TokenSupplier { + + private static final Logger logger = Logger.getLogger(FileSecurityTokenSupplier.class.getName()); + + private final SessionKeyPairSupplier sessionKeyPairSupplier; + private final String sessionTokenPath; + private volatile SecurityTokenSupplier.SecurityToken securityToken; + private long minTokenLifetime; + + FileSecurityTokenSupplier(SessionKeyPairSupplier sessKeyPairSupplier, + String sessionTokenPath) { + this.sessionKeyPairSupplier = sessKeyPairSupplier; + this.sessionTokenPath = sessionTokenPath; + } + + @Override + public String getSecurityToken() { + return refreshAndGetSecurityToken(); + } + + @Override + public String getStringClaim(String key) { + if (securityToken == null) { + refreshAndGetSecurityToken(); + } + return securityToken.getStringClaim(key); + } + + @Override + public void setMinTokenLifetime(long lifetimeMS) { + this.minTokenLifetime = lifetimeMS; + } + + @Override + public void close() { + } + + @Override + public void prepare(NoSQLHandleConfig config) { + } + + private synchronized String refreshAndGetSecurityToken() { + logTrace(logger, "Refreshing session keys"); + sessionKeyPairSupplier.refreshKeys(); + + try { + logTrace(logger, "Getting security token from file."); + SecurityToken token = getSecurityTokenFromFile(); + token.validate(minTokenLifetime, logger); + securityToken = token; + return securityToken.getSecurityToken(); + } catch (Exception e) { + throw new SecurityInfoNotReadyException(e.getMessage(), e); + } + } + + SecurityToken getSecurityTokenFromFile() { + KeyPair keyPair = sessionKeyPairSupplier.getKeyPair(); + if (keyPair == null) { + throw new IllegalArgumentException( + "Keypair for session was not provided"); + } + + String sessToken = null; + try { + sessToken = new String( + Files.readAllBytes(Paths.get(sessionTokenPath)), + Charset.defaultCharset()); + } catch (IOException e) { + throw new RuntimeException( + "Unable to read session token from " + sessionTokenPath, e); + } + + return new SecurityToken(sessToken, sessionKeyPairSupplier); + } +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/FixedSecurityTokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/FixedSecurityTokenSupplier.java new file mode 100644 index 00000000..32457d34 --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/FixedSecurityTokenSupplier.java @@ -0,0 +1,60 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import oracle.nosql.driver.NoSQLHandleConfig; + +import java.util.logging.Logger; + +/** + * @hidden + * Internal use only + *

+ * The interface to supply security token for resource principal from fixed + * String content. + * Reference to the OCI SDK for Java + * com.oracle.bmc.auth.internal.FixedContentResourcePrincipalFederationClient + */ +class FixedSecurityTokenSupplier + implements TokenSupplier { + + private static final Logger logger = Logger.getLogger(FixedSecurityTokenSupplier.class.getName()); + + private final SecurityTokenSupplier.SecurityToken securityToken; + private long minTokenLifetime; + + FixedSecurityTokenSupplier(SessionKeyPairSupplier sessionKeySupplier, + String sessionToken) { + this.securityToken = new SecurityTokenSupplier.SecurityToken(sessionToken, + sessionKeySupplier); + } + + @Override + public String getSecurityToken() { + securityToken.validate(minTokenLifetime, logger); + return securityToken.getSecurityToken(); + } + + @Override + public String getStringClaim(String key) { + return securityToken.getStringClaim(key); + } + + @Override + public void setMinTokenLifetime(long lifetimeMS) { + this.minTokenLifetime = lifetimeMS; + } + + @Override + public void close() { + } + + @Override + public void prepare(NoSQLHandleConfig config) { + } +} \ No newline at end of file diff --git a/driver/src/main/java/oracle/nosql/driver/iam/InstanceMetadataHelper.java b/driver/src/main/java/oracle/nosql/driver/iam/InstanceMetadataHelper.java index f3e79f58..48dbddfa 100644 --- a/driver/src/main/java/oracle/nosql/driver/iam/InstanceMetadataHelper.java +++ b/driver/src/main/java/oracle/nosql/driver/iam/InstanceMetadataHelper.java @@ -95,9 +95,11 @@ static InstanceMetadata fetchMetadata(int timeout, Logger logger) { } logTrace(logger, "Instance metadata " + response.getOutput()); - String insRegion = findRegion(response.getOutput()); - logTrace(logger, "Instance region " + insRegion); - return new InstanceMetadata(insRegion, baseMetadataURL); + String insRegion = findKeyValue("canonicalRegionName", response.getOutput()); + String instanceId = findKeyValue("id", response.getOutput()); + logTrace(logger, "Instance region: " + insRegion + + ", Instance id: " + instanceId); + return new InstanceMetadata(insRegion, instanceId, baseMetadataURL); } finally { if (client != null) { client.shutdown(); @@ -111,7 +113,7 @@ private static HttpHeaders headers() { .set(AUTHORIZATION, AUTHORIZATION_HEADER_VALUE); } - private static String findRegion(String response) { + private static String findKeyValue(String key, String response) { try { JsonParser parser = factory.createParser(response); if (parser.getCurrentToken() == null) { @@ -119,7 +121,7 @@ private static String findRegion(String response) { } while (parser.getCurrentToken() != null) { String field = findField( - response, parser, "canonicalRegionName"); + response, parser, key); if (field != null) { parser.nextToken(); return parser.getText(); @@ -135,11 +137,13 @@ private static String findRegion(String response) { } static class InstanceMetadata { - private String region; - private String baseMetadataURL; + private final String region; + private final String id; + private final String baseMetadataURL; - InstanceMetadata(String region, String baseMetadataURL) { + InstanceMetadata(String region, String id, String baseMetadataURL) { this.region = region; + this.id = id; this.baseMetadataURL = baseMetadataURL; } @@ -147,6 +151,10 @@ String getRegion() { return region; } + String getId() { + return id; + } + String getBaseURL() { return baseMetadataURL; } diff --git a/driver/src/main/java/oracle/nosql/driver/iam/InstancePrincipalsProvider.java b/driver/src/main/java/oracle/nosql/driver/iam/InstancePrincipalsProvider.java index 2fc80fa1..1b7c7247 100644 --- a/driver/src/main/java/oracle/nosql/driver/iam/InstancePrincipalsProvider.java +++ b/driver/src/main/java/oracle/nosql/driver/iam/InstancePrincipalsProvider.java @@ -7,26 +7,15 @@ package oracle.nosql.driver.iam; -import static oracle.nosql.driver.iam.Utils.getIAMURL; -import static oracle.nosql.driver.util.HttpConstants.AUTHORIZATION; - import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashSet; -import java.util.Set; import java.util.logging.Logger; import oracle.nosql.driver.NoSQLHandleConfig; import oracle.nosql.driver.Region; import oracle.nosql.driver.Region.RegionProvider; -import oracle.nosql.driver.iam.CertificateSupplier.DefaultCertificateSupplier; -import oracle.nosql.driver.iam.CertificateSupplier.URLResourceDetails; -import oracle.nosql.driver.iam.InstanceMetadataHelper.InstanceMetadata; import oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityTokenBasedProvider; import oracle.nosql.driver.iam.SessionKeyPairSupplier.DefaultSessionKeySupplier; -import oracle.nosql.driver.iam.SessionKeyPairSupplier.JDKKeyPairSupplier; /** * Internal use only @@ -36,12 +25,12 @@ * token issued by IAM to do the actual request signing. * @hidden */ -public class InstancePrincipalsProvider +class InstancePrincipalsProvider implements AuthenticationProfileProvider, RegionProvider, SecurityTokenBasedProvider { - protected final SecurityTokenSupplier tokenSupplier; + protected final TokenSupplier tokenSupplier; protected final DefaultSessionKeySupplier sessionKeySupplier; private final Region region; @@ -51,7 +40,7 @@ public class InstancePrincipalsProvider * @param keyPairSupplier the SessionKeyPairSupplier * @param region the region */ - public InstancePrincipalsProvider(SecurityTokenSupplier tokenSupplier, + public InstancePrincipalsProvider(TokenSupplier tokenSupplier, SessionKeyPairSupplier keyPairSupplier, Region region) { this.tokenSupplier = tokenSupplier; @@ -111,182 +100,7 @@ public static InstancePrincipalsProviderBuilder builder() { * Builder of InstancePrincipalsProvider * @hidden */ - public static class InstancePrincipalsProviderBuilder { - /* The default value for HTTP request timeouts in milliseconds */ - private static final int DEFAULT_TIMEOUT_MS = 5_000; - - /* The default purpose value in federation requests against IAM */ - private static final String DEFAULT_PURPOSE = "DEFAULT"; - - /* - * IAM federation endpoint, or null if detecting from instance metadata. - */ - private String federationEndpoint; - - /* Instance metadata */ - private InstanceMetadata instanceMetadata; - - /* - * The leaf certificate, or null if detecting from instance metadata. - */ - private CertificateSupplier leafCertificateSupplier; - - /* - * Intermediate certificates or null if detecting from instance metadata. - */ - private Set intermediateCertificateSuppliers; - - /* - * Session key pair supplier. - */ - private SessionKeyPairSupplier sessSupplier = new JDKKeyPairSupplier(); - - /* - * Tenant id, or null if detecting from instance metadata. - */ - private String tenantId; - private String purpose = DEFAULT_PURPOSE; - private int timeout = DEFAULT_TIMEOUT_MS; - private Region region; - private Logger logger; - - public String getFederationEndpoint() { - return federationEndpoint; - } - - /** - * @hidden - * @param federationEndpoint the endpoint - * @return this - */ - public InstancePrincipalsProviderBuilder - setFederationEndpoint(String federationEndpoint) { - - this.federationEndpoint = federationEndpoint; - return this; - } - - public CertificateSupplier getLeafCertificateSupplier() { - return leafCertificateSupplier; - } - - /** - * @hidden - * @param supplier the supplier - * @return this - */ - public InstancePrincipalsProviderBuilder - setLeafCertificateSupplier(CertificateSupplier supplier) { - - this.leafCertificateSupplier = supplier; - return this; - } - - public String getTenantId() { - return tenantId; - } - - /** - * @hidden - * @param tenantId the tenant - * @return this - */ - public InstancePrincipalsProviderBuilder setTenantId(String tenantId) { - this.tenantId = tenantId; - return this; - } - - public String getPurpose() { - return purpose; - } - - /** - * @hidden - * @param purpose the purpose - * @return this - */ - public InstancePrincipalsProviderBuilder setPurpose(String purpose) { - this.purpose = purpose; - return this; - } - - public SessionKeyPairSupplier getSesssionKeyPairSupplier() { - return sessSupplier; - } - - /** - * @hidden - * @param sessSupplier the supplier - * @return this - */ - public InstancePrincipalsProviderBuilder - setSessionKeyPairSupplier(SessionKeyPairSupplier sessSupplier) { - this.sessSupplier = sessSupplier; - return this; - } - - public int getTimeout() { - return timeout; - } - - /** - * @hidden - * @param timeout the timeout - * @return this - */ - public InstancePrincipalsProviderBuilder setTimeout(int timeout) { - this.timeout = timeout; - return this; - } - - public Logger getLogger() { - return logger; - } - - /** - * @hidden - * @param logger the logger - * @return this - */ - public InstancePrincipalsProviderBuilder setLogger(Logger logger) { - this.logger = logger; - return this; - } - - public Set getIntermediateCertificateSuppliers() { - return intermediateCertificateSuppliers; - } - - /** - * @hidden - * @param suppliers suppliers - * @return this - */ - public InstancePrincipalsProviderBuilder - setIntermediateCertificateSuppliers( - Set suppliers) { - this.intermediateCertificateSuppliers = suppliers; - return this; - } - - /** - * Returns the region if set - * @return the region or null if not set - */ - public Region getRegion() { - return region; - } - - /** - * Sets a region - * @param r the region - * @return this - */ - public InstancePrincipalsProviderBuilder setRegion(Region r) { - this.region = r; - return this; - } - + public static class InstancePrincipalsProviderBuilder extends BaseProviderBuilder { /** * Builds the InstancePrincipalsProvider instance * @return the instance @@ -298,92 +112,20 @@ public InstancePrincipalsProvider build() { autoDetectEndpointUsingMetadataUrl(); autoDetectCertificatesUsingMetadataUrl(); - SecurityTokenSupplier tokenSupplier = - new SecurityTokenSupplier(federationEndpoint, - tenantId, - leafCertificateSupplier, - intermediateCertificateSuppliers, - sessSupplier, - purpose, - timeout, - logger); + TokenSupplier tokenSupplier = + new SecurityTokenSupplier(federationEndpoint, + tenantId, + leafCertificateSupplier, + intermediateCertificateSuppliers, + sessionKeySupplier, + purpose, + timeout, + logger); return new InstancePrincipalsProvider(tokenSupplier, - sessSupplier, - region); - } - - /* - * Auto detects the endpoint that should be used when talking to - * IAM, if no endpoint has been configured already. - */ - private void autoDetectEndpointUsingMetadataUrl() { - if (federationEndpoint != null) { - return; - } - - final String insRegion = getInstanceMetadata().getRegion(); - federationEndpoint = getIAMURL(insRegion); - if (region == null) { - region = Region.fromRegionId(insRegion); - } - if (federationEndpoint == null) { - throw new IllegalArgumentException( - "Unable to find IAM URL for unregistered region " + - region + ", specify the IAM URL instead"); - } - } - - /* - * Auto detects and configures the certificates needed - * using Instance metadata. - */ - private void autoDetectCertificatesUsingMetadataUrl() { - try { - if (leafCertificateSupplier == null) { - leafCertificateSupplier = new DefaultCertificateSupplier( - getURLDetails(getInstanceMetadata().getBaseURL() + - "identity/cert.pem"), - getURLDetails(getInstanceMetadata().getBaseURL() + - "identity/key.pem"), - (char[]) null); - } - - if (tenantId == null) { - tenantId = Utils.getTenantId(leafCertificateSupplier - .getCertificateAndKeyPair().getCertificate()); - } - - if (intermediateCertificateSuppliers == null) { - intermediateCertificateSuppliers = new HashSet<>(); - - intermediateCertificateSuppliers.add( - new DefaultCertificateSupplier( - getURLDetails(getInstanceMetadata().getBaseURL() + - "identity/intermediate.pem"), - null, - (char[]) null)); - } - } catch (MalformedURLException ex) { - throw new IllegalArgumentException( - "The instance metadata service url is invalid.", ex); - } + sessionKeySupplier, + region); } - private URLResourceDetails getURLDetails(String url) - throws MalformedURLException { - - return new URLResourceDetails(new URL(url)).addHeader( - AUTHORIZATION, - InstanceMetadataHelper.AUTHORIZATION_HEADER_VALUE); - } - - private InstanceMetadata getInstanceMetadata() { - if (instanceMetadata == null) { - instanceMetadata = InstanceMetadataHelper - .fetchMetadata(timeout, logger); - } - return instanceMetadata; - } } } diff --git a/driver/src/main/java/oracle/nosql/driver/iam/KeyPairProvider.java b/driver/src/main/java/oracle/nosql/driver/iam/KeyPairProvider.java new file mode 100644 index 00000000..2590ede9 --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/KeyPairProvider.java @@ -0,0 +1,116 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityTokenBasedProvider; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Resource Principals V2 using public/private key to sign the request. This class provides the + * authentication based on public/private key. + */ +class KeyPairProvider + implements AuthenticationProfileProvider, + SecurityTokenBasedProvider { + + private final String resourceId; + private final byte[] privateKeyBytes; + private final char[] passphrase; + private final String tenancyId; + private final String resourcePrincipalVersion; + + /** + * Constructor of KeyPairProvider + * + * @param resourceId resource id of the resource + * @param privateKeyStream private key stream to sign the request + * @param passphrase passphrase for the private key + * @param tenancyId tenancy id of the resource + * @param resourcePrincipalVersion resource principal version + */ + public KeyPairProvider( + final String resourceId, + final InputStream privateKeyStream, + final char[] passphrase, + final String tenancyId, + final String resourcePrincipalVersion) { + this.resourceId = resourceId; + this.tenancyId = tenancyId; + this.resourcePrincipalVersion = resourcePrincipalVersion; + this.passphrase = passphrase; + try { + // read once and store bytes + this.privateKeyBytes = Utils.toByteArray(privateKeyStream); + } catch (IOException ex) { + throw new IllegalArgumentException("Unable to read private key stream.", ex); + } finally { + if (privateKeyStream != null) { + try { privateKeyStream.close(); } catch (IOException ignore) {} + } + } + } + + /** + * Returns the keyId used to sign requests. + * + * @return The keyId. + */ + @Override + public String getKeyId() { + if (tenancyId != null) { + if (Utils.isNotBlank(resourcePrincipalVersion) + && resourcePrincipalVersion.equals("2.1.1")) { + return "resource/v2.1.1/" + this.tenancyId + "/" + this.resourceId; + } + if (Utils.isNotBlank(resourcePrincipalVersion) + && resourcePrincipalVersion.equals("2.1.2")) { + return "resource/v2.1.2/" + this.tenancyId + "/" + this.resourceId; + } + } + return "resource/v2.1/" + this.resourceId; + } + + /** + * Returns a new InputStream to the private key. This stream should be closed by the caller, + * implementations should return new streams each time. + * + * @return A new InputStream. + */ + @Override + public InputStream getPrivateKey() { + return new ByteArrayInputStream(this.privateKeyBytes); + } + + /** + * Returns the optional pass phrase for the (encrypted) private key, as a character array. + * It returns a clone of the original passphrase, so that caller may overwrite/clear the + * array they receive. + * + * @return The pass phrase as character array, or null if not applicable + */ + @Override + public char[] getPassphraseCharacters() { + return (this.passphrase == null) ? null : this.passphrase.clone(); + } + + /** + * Returns the Resource ID of the resource + * + * @return the resourceId + */ + public String getResourceId() { + return resourceId; + } + + @Override + public void setMinTokenLifetime(long lifetimeMS) {} + +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalProvider.java b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalProvider.java index 1159f982..6af35700 100644 --- a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalProvider.java +++ b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalProvider.java @@ -7,19 +7,28 @@ package oracle.nosql.driver.iam; +import static oracle.nosql.driver.iam.Utils.isBlank; +import static oracle.nosql.driver.iam.Utils.isNotBlank; +import static oracle.nosql.driver.iam.Utils.logTrace; + import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.FileNotFoundException; import java.util.logging.Logger; import oracle.nosql.driver.Region; import oracle.nosql.driver.Region.RegionProvider; -import oracle.nosql.driver.iam.ResourcePrincipalTokenSupplier.FileSecurityTokenSupplier; -import oracle.nosql.driver.iam.ResourcePrincipalTokenSupplier.FixedSecurityTokenSupplier; import oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityTokenBasedProvider; import oracle.nosql.driver.iam.SessionKeyPairSupplier.DefaultSessionKeySupplier; import oracle.nosql.driver.iam.SessionKeyPairSupplier.FileKeyPairSupplier; import oracle.nosql.driver.iam.SessionKeyPairSupplier.FixedKeyPairSupplier; +import oracle.nosql.driver.NoSQLHandleConfig; + +import java.nio.file.Files; +import java.nio.file.Path; /** * @hidden @@ -64,100 +73,79 @@ * * */ -class ResourcePrincipalProvider +public class ResourcePrincipalProvider implements AuthenticationProfileProvider, RegionProvider, SecurityTokenBasedProvider { + private static final Logger logger = Logger.getLogger(ResourcePrincipalProvider.class.getName()); /* Environment variable names used to fetch artifacts */ private static final String OCI_RESOURCE_PRINCIPAL_VERSION = - "OCI_RESOURCE_PRINCIPAL_VERSION"; - private static final String RP_VERSION_2_2 = "2.2"; - private static final String OCI_RESOURCE_PRINCIPAL_RPST = - "OCI_RESOURCE_PRINCIPAL_RPST"; + "OCI_RESOURCE_PRINCIPAL_VERSION"; + protected static final String RP_VERSION_1_1 = "1.1"; + protected static final String RP_VERSION_2_1 = "2.1"; + protected static final String RP_VERSION_2_1_1 = "2.1.1"; + protected static final String RP_VERSION_2_1_2 = "2.1.2"; + protected static final String RP_VERSION_2_2 = "2.2"; + protected static final String RP_VERSION_3_0 = "3.0"; + protected static final String OCI_RESOURCE_PRINCIPAL_RPST = + "OCI_RESOURCE_PRINCIPAL_RPST"; + protected static final String OCI_RESOURCE_PRINCIPAL_REGION = + "OCI_RESOURCE_PRINCIPAL_REGION"; private static final String OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM = - "OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM"; + "OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM"; private static final String OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE = - "OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE"; - private static final String OCI_RESOURCE_PRINCIPAL_REGION = - "OCI_RESOURCE_PRINCIPAL_REGION"; - - private final ResourcePrincipalTokenSupplier tokenSupplier; - private final DefaultSessionKeySupplier sessionKeySupplier; - private final Region region; - - public static ResourcePrincipalProvider build(Logger logger) { - if (logger == null) { - logger = Logger.getLogger(ResourcePrincipalProvider.class.getName()); - } - String version = System.getenv(OCI_RESOURCE_PRINCIPAL_VERSION); - if (version == null) { - throw new IllegalArgumentException( - OCI_RESOURCE_PRINCIPAL_VERSION + - " environment variable missing"); - } - if (!version.equals(RP_VERSION_2_2)) { - throw new IllegalArgumentException( - OCI_RESOURCE_PRINCIPAL_VERSION + - " has unknown value " + version); - } + "OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE"; + private static final String OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT = + "OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT"; + private static final String OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT = + "OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT"; + private static final String OCI_RESOURCE_PRINCIPAL_RPT_PATH = "OCI_RESOURCE_PRINCIPAL_RPT_PATH"; + static final String OCI_RESOURCE_PRINCIPAL_REGION_ENV_VAR_NAME = + "OCI_RESOURCE_PRINCIPAL_REGION"; + private static final String OCI_RESOURCE_PRINCIPAL_RESOURCE_ID = + "OCI_RESOURCE_PRINCIPAL_RESOURCE_ID"; + private static final String OCI_RESOURCE_PRINCIPAL_TENANCY_ID = + "OCI_RESOURCE_PRINCIPAL_TENANCY_ID"; + private static final String OCI_RESOURCE_PRINCIPAL_SECURITY_CONTEXT = + "OCI_RESOURCE_PRINCIPAL_SECURITY_CONTEXT"; - String rpPrivateKey = System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM); - String kp = System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE); + private static final String DEFAULT_OCI_RESOURCE_PRINCIPAL_RPT_PATH_FORV2_1_OR_2_1_1 = + "20180711/resourcePrincipalTokenV2"; + private static final String DEFAULT_OCI_RESOURCE_PRINCIPAL_RPT_PATH_FORV212 = + "20180711/resourcePrincipalTokenV212"; - SessionKeyPairSupplier sessKeySupplier; - if (rpPrivateKey == null) { - throw new IllegalArgumentException( - OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM + - " environment variable missing"); - } + private static final String RP_DEBUG_INFORMATION_LOG = + "\nResource principals authentication can only be used in certain OCI services. Please check that the OCI service you're running this code from supports Resource principals." + + "\nSee https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdk_authentication_methods.htm#sdk_authentication_methods_resource_principal for more info."; - if (new File(rpPrivateKey).isAbsolute()) { - if (kp != null && !new File(kp).isAbsolute()) { - throw new IllegalArgumentException( - "cannot mix path and constant settings for " + - OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM + " and " + - OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE); - } - sessKeySupplier = new FileKeyPairSupplier(rpPrivateKey, kp); - } else { - char[] passPhraseChars = null; - if (kp != null) { - passPhraseChars = kp.toCharArray(); - } - sessKeySupplier = new FixedKeyPairSupplier(rpPrivateKey, - passPhraseChars); - } + private final TokenSupplier tokenSupplier; + private final DefaultSessionKeySupplier sessionKeySupplier; + private final Region region; - String rpst = System.getenv(OCI_RESOURCE_PRINCIPAL_RPST); - if (rpst == null) { - throw new IllegalArgumentException( - OCI_RESOURCE_PRINCIPAL_RPST + " environment variable missing"); - } - ResourcePrincipalTokenSupplier tokenSupplier; - if (new File(rpst).isAbsolute()) { - tokenSupplier = new FileSecurityTokenSupplier( - sessKeySupplier, rpst, logger); - } else { - tokenSupplier = new FixedSecurityTokenSupplier( - sessKeySupplier, rpst, logger); - } + /** + * Constructor of ResourcePrincipalAuthenticationDetailsProvider. + * + * @param tokenSupplier token supplier implementation. + * @param sessionKeyPairSupplier session key supplier implementation. + * @param region the region + */ + ResourcePrincipalProvider(TokenSupplier tokenSupplier, + SessionKeyPairSupplier sessionKeyPairSupplier, + Region region) { + this.tokenSupplier = tokenSupplier; + this.sessionKeySupplier = new DefaultSessionKeySupplier(sessionKeyPairSupplier); + this.region = region; + } - String rpRegion = System.getenv(OCI_RESOURCE_PRINCIPAL_REGION); - if (rpRegion == null) { - throw new IllegalArgumentException( - OCI_RESOURCE_PRINCIPAL_REGION + " environment variable missing"); - } - Region r = Region.fromRegionId(rpRegion); - return new ResourcePrincipalProvider(tokenSupplier, sessKeySupplier, r); + @Override + public void prepare(NoSQLHandleConfig config) { + tokenSupplier.prepare(config); } - ResourcePrincipalProvider(ResourcePrincipalTokenSupplier tkSupplier, - SessionKeyPairSupplier keyPairSupplier, - Region region) { - this.tokenSupplier = tkSupplier; - this.sessionKeySupplier = new DefaultSessionKeySupplier(keyPairSupplier); - this.region = region; + @Override + public void close() { + tokenSupplier.close(); } @Override @@ -185,16 +173,487 @@ public void setMinTokenLifetime(long lifetimeMS) { tokenSupplier.setMinTokenLifetime(lifetimeMS); } + public String getClaim(String key) { + return tokenSupplier.getStringClaim(key); + } + + public static ResourcePrincipalProviderBuilder builder(){ + return new ResourcePrincipalProviderBuilder(); + } + /** - * @hidden - * Internal only + * Cloud service only. *

- * Session tokens carry JWT claims. Permit the retrieval of the - * value of those claims from the token. - * @param key the name of a claim in the session token - * @return the claim value. + * Builder of ResourcePrincipalProvider + * @hidden */ - String getClaim(String key) { - return tokenSupplier.getStringClaim(key); + public static class ResourcePrincipalProviderBuilder extends BaseProviderBuilder { + + ResourcePrincipalProviderBuilder() {} + + /** The endpoint that can provide the resource principal token. */ + protected String resourcePrincipalTokenEndpoint; + + /** The path provider for the resource principal token. */ + protected RptPathProvider resourcePrincipalTokenPathProvider; + + /** The configuration for the security context. */ + protected String securityContext; + + /** Configures the resourcePrincipalTokenPathProvider to use. */ + public ResourcePrincipalProviderBuilder setResourcePrincipalTokenPathProvider( + RptPathProvider resourcePrincipalTokenPathProvider + ) { + this.resourcePrincipalTokenPathProvider = resourcePrincipalTokenPathProvider; + return this; + } + + /** Configures the resourcePrincipalTokenEndpoint to use. */ + public ResourcePrincipalProviderBuilder setResourcePrincipalTokenEndpoint( + String resourcePrincipalTokenEndpoint + ) { + this.resourcePrincipalTokenEndpoint = resourcePrincipalTokenEndpoint; + return this; + } + + /** Set value for the security context to use. */ + public ResourcePrincipalProviderBuilder setSecurityContext(String securityContext) { + this.securityContext = securityContext; + return this; + } + + public ResourcePrincipalProvider build() { + if (logger == null) { + logger = Logger.getLogger(getClass().getName()); + } + String version = System.getenv(OCI_RESOURCE_PRINCIPAL_VERSION); + if (version == null) { + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_VERSION + + " environment variable missing"); + } + + switch (version) { + case RP_VERSION_1_1: + final String resourcePrincipalRptEndpoint = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT); + final String resourcePrincipalRpstEndpoint = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT); + return build_1_1( + resourcePrincipalRptEndpoint, resourcePrincipalRpstEndpoint); + case RP_VERSION_2_1: + case RP_VERSION_2_1_1: + final String resourcePrincipalRptEndpointFor2_1_or_2_1_1 = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT); + final String resourcePrincipalRpstEndpointForLeafResourceFor2_1_or_2_1_1 = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT); + final String resourcePrincipalResourceIdForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_RESOURCE_ID); + final String resourcePrincipalTenancyIdForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_TENANCY_ID); + final String resourcePrincipalPrivateKeyForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM); + final String resourcePrincipalPassphraseForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE); + return build_2_1_or_2_1_1( + resourcePrincipalRptEndpointFor2_1_or_2_1_1, + resourcePrincipalRpstEndpointForLeafResourceFor2_1_or_2_1_1, + resourcePrincipalResourceIdForLeafResource, + resourcePrincipalTenancyIdForLeafResource, + resourcePrincipalPrivateKeyForLeafResource, + resourcePrincipalPassphraseForLeafResource, + version); + case RP_VERSION_2_1_2: + final String resourcePrincipalRptEndpointFor2_1_2 = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT); + final String resourcePrincipalRpstEndpointForLeafResourceFor2_1_2 = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT); + final String resourcePrincipalResourceIdForLeafResourceFor2_1_2 = + System.getenv(OCI_RESOURCE_PRINCIPAL_RESOURCE_ID); + final String resourcePrincipalTenancyIdForLeafResourceFor2_1_2 = + System.getenv(OCI_RESOURCE_PRINCIPAL_TENANCY_ID); + final String resourcePrincipalSecurityContext = + System.getenv(OCI_RESOURCE_PRINCIPAL_SECURITY_CONTEXT); + final String resourcePrincipalPrivateKeyForLeafResourceFor2_1_2 = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM); + final String resourcePrincipalPassphraseForLeafResourceFor2_1_2 = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE); + final String resourcePrincipalTokenPath = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_PATH); + return build_2_1_2( + resourcePrincipalRptEndpointFor2_1_2, + resourcePrincipalRpstEndpointForLeafResourceFor2_1_2, + resourcePrincipalTokenPath, + resourcePrincipalSecurityContext, + resourcePrincipalResourceIdForLeafResourceFor2_1_2, + resourcePrincipalTenancyIdForLeafResourceFor2_1_2, + resourcePrincipalPrivateKeyForLeafResourceFor2_1_2, + resourcePrincipalPassphraseForLeafResourceFor2_1_2, + version); + case RP_VERSION_2_2: + final String resourcePrincipalPrivateKey = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM); + final String resourcePrincipalPassphrase = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE); + final String resourcePrincipalRegion = + System.getenv(OCI_RESOURCE_PRINCIPAL_REGION); + final String resourcePrincipalSessionToken = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST); + return build_2_2( + resourcePrincipalPrivateKey, + resourcePrincipalPassphrase, + resourcePrincipalRegion, + resourcePrincipalSessionToken); + case RP_VERSION_3_0: + return build_3_0(); + default: + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_VERSION + + " has unknown value " + version); + } + } + + /** + * Helper method that interprets the runtime environment to build a v1.1-configured client + * + * @return ResourcePrincipalProvider + */ + public ResourcePrincipalProvider build_1_1( + String ociResourcePrincipalRptEndpoint, String ociResourcePrincipalRpstEndpoint + ) { + resourcePrincipalTokenEndpoint = ociResourcePrincipalRptEndpoint; + if (ociResourcePrincipalRpstEndpoint != null) { + federationEndpoint = ociResourcePrincipalRpstEndpoint; + } + + // auto detect endpoint and region + autoDetectEndpointUsingMetadataUrl(); + + tokenSupplier = createTokenSupplier(sessionKeySupplier); + return new ResourcePrincipalProvider( + tokenSupplier, sessionKeySupplier, region); + } + + /** + * Helper method that interprets the runtime environment to build a v2.1 or 2.1.1-configured + * client + * + * @return ResourcePrincipalProvider + */ + public ResourcePrincipalProvider build_2_1_or_2_1_1( + String resourcePrincipalRptEndpoint, + String resourcePrincipalRpstEndpoint, + String resourcePrincipalResourceId, + String resourcePrincipalTenancyId, + String resourcePrincipalPrivateKey, + String resourcePrincipalPassphrase, + String resourcePrincipalVersion) { + + if(isBlank(resourcePrincipalRptEndpoint)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalTokenEndpoint cannot be blank"); + } + if(isBlank(resourcePrincipalRpstEndpoint)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalTokenRpstEndpoint cannot be blank"); + } + if(isBlank(resourcePrincipalResourceId)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalResourceId cannot be blank"); + } + if(isBlank(resourcePrincipalPrivateKey)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalPrivateKey cannot be blank"); + } + if (resourcePrincipalVersion.equals("2.1.1")) { + if(isBlank(resourcePrincipalTenancyId)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalTenancyId cannot be blank"); + } + } + + sessionKeySupplier = + getSessionKeySupplierFromPemAndPassphrase( + resourcePrincipalPrivateKey, + resourcePrincipalPassphrase + ); + + KeyPairProvider provider = + getKeyPairProvider( + resourcePrincipalResourceId, + resourcePrincipalPrivateKey, + resourcePrincipalPassphrase, + resourcePrincipalTenancyId, + resourcePrincipalVersion); + + String resourcePrincipalTokenPath = createTokenPath( + DEFAULT_OCI_RESOURCE_PRINCIPAL_RPT_PATH_FORV2_1_OR_2_1_1, + resourcePrincipalResourceId + ); + + tokenSupplier = + new ResourcePrincipalV2TokenSupplier( + resourcePrincipalRptEndpoint, + resourcePrincipalRpstEndpoint, + resourcePrincipalTokenPath, + sessionKeySupplier, + provider, + null); + + // auto detect region + autoDetectEndpointUsingMetadataUrl(); + + return new ResourcePrincipalProvider( + tokenSupplier, sessionKeySupplier, region); + } + + /** + * Helper method that interprets the runtime environment to build a v2.1.2-configured client + * + * @return ResourcePrincipalProvider + */ + public ResourcePrincipalProvider build_2_1_2( + String resourcePrincipalRptEndpoint, + String resourcePrincipalRpstEndpoint, + String resourcePrincipalTokenPath, + String securityContext, + String resourcePrincipalResourceId, + String resourcePrincipalTenancyId, + String resourcePrincipalPrivateKey, + String resourcePrincipalPassphrase, + String resourcePrincipalVersion) { + + if(isNotBlank(this.securityContext)) { + securityContext = this.securityContext; + logTrace(logger, "Security context provided via the builder overrides" + + " the value provided via environment variable"); + } + if(isBlank(resourcePrincipalTokenPath)) { + resourcePrincipalTokenPath = DEFAULT_OCI_RESOURCE_PRINCIPAL_RPT_PATH_FORV212; + } + + if(isBlank(resourcePrincipalRptEndpoint)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalTokenEndpoint cannot be blank"); + } + if(isBlank(resourcePrincipalRpstEndpoint)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalTokenRpstEndpoint cannot be blank"); + } + if(isBlank(securityContext)) { + throw new IllegalArgumentException("required: " + + "securityContext cannot be blank"); + } + if(isBlank(resourcePrincipalResourceId)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalResourceId cannot be blank"); + } + if(isBlank(resourcePrincipalPrivateKey)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalPrivateKey cannot be blank"); + } + if(isBlank(resourcePrincipalTenancyId)) { + throw new IllegalArgumentException("required: " + + "ResourcePrincipalTenancyId cannot be blank"); + } + + + sessionKeySupplier = + getSessionKeySupplierFromPemAndPassphrase( + resourcePrincipalPrivateKey, + resourcePrincipalPassphrase + ); + + KeyPairProvider provider = + getKeyPairProvider( + resourcePrincipalResourceId, + resourcePrincipalPrivateKey, + resourcePrincipalPassphrase, + resourcePrincipalTenancyId, + resourcePrincipalVersion); + + + resourcePrincipalTokenPath = + createTokenPath(resourcePrincipalTokenPath, resourcePrincipalResourceId); + + tokenSupplier = + new ResourcePrincipalV2TokenSupplier( + resourcePrincipalRptEndpoint, + resourcePrincipalRpstEndpoint, + resourcePrincipalTokenPath, + sessionKeySupplier, + provider, + securityContext); + + // auto detect region + autoDetectEndpointUsingMetadataUrl(); + + return new ResourcePrincipalProvider( + tokenSupplier, sessionKeySupplier, region); + } + + public ResourcePrincipalProvider build_2_2( + String rpPrivateKey, + String kp, + String rpRegion, + String rpst + ) { + final SessionKeyPairSupplier sessKeySupplier = + getSessionKeySupplierFromPemAndPassphrase(rpPrivateKey, kp); + + if (rpst == null) { + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_RPST + " environment variable missing"); + } + final TokenSupplier tokenSupplier; + if (new File(rpst).isAbsolute()) { + logTrace(logger, "Valid file for RPST." + + " Creating instance of FileSecurityTokenSupplier"); + tokenSupplier = new FileSecurityTokenSupplier( + sessKeySupplier, rpst); + } else { + logTrace(logger, "Loading RPST from content provided. Creating instance of" + + " FixedSecurityTokenSupplier"); + tokenSupplier = new FixedSecurityTokenSupplier( + sessKeySupplier, rpst); + } + + if (rpRegion == null) { + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_REGION + " environment variable missing"); + } + final Region region = Region.fromRegionId(rpRegion); + return new ResourcePrincipalProvider(tokenSupplier, sessKeySupplier, region); + } + + /** + * Helper method that interprets the runtime environment to build a v3.0-configured client + * + * @return ResourcePrincipalAuthenticationDetailsProvider + */ + public ResourcePrincipalProvider build_3_0() { + return ResourcePrincipalV3Provider.builder().build(); + } + + private KeyPairProvider getKeyPairProvider( + String resourcePrincipalResourceId, + String resourcePrincipalPrivateKey, + String resourcePrincipalPassphrase, + String tenancyId, + String resourcePrincipalVersion) { + final InputStream privateKeyStream; + final String passphrase; + if (new File(resourcePrincipalPrivateKey).isAbsolute()) { + if (resourcePrincipalPassphrase != null + && !new File(resourcePrincipalPassphrase).isAbsolute()) { + throw new IllegalArgumentException( + "cannot mix path and constant settings for " + + OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM + " and " + + OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE); + } + try { + privateKeyStream = new FileInputStream(resourcePrincipalPrivateKey); + Path passphrasePath = + (resourcePrincipalPassphrase != null) + ? new File(resourcePrincipalPassphrase).toPath() + : null; + if (passphrasePath != null) { + passphrase = new String(Files.readAllBytes(passphrasePath)); + } else passphrase = null; + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("Can't find file for private key", e); + } catch (IOException e) { + throw new RuntimeException("cannot read the passphrase", e); + } + + } else { + passphrase = resourcePrincipalPassphrase; + privateKeyStream = + new ByteArrayInputStream(resourcePrincipalPrivateKey.getBytes()); + } + + return new KeyPairProvider( + resourcePrincipalResourceId, + privateKeyStream, + (passphrase != null) ? passphrase.toCharArray() : null, + tenancyId, + resourcePrincipalVersion); + } + + private ResourcePrincipalV1TokenSupplier createTokenSupplier( + SessionKeyPairSupplier sessionKeyPairSupplier) { + createRptPathProvider(); + + InstancePrincipalsProvider provider = + InstancePrincipalsProvider.builder() + .setFederationEndpoint(federationEndpoint) + .setLeafCertificateSupplier(leafCertificateSupplier) + .setIntermediateCertificateSuppliers(intermediateCertificateSuppliers) + // InstancePrincipalsProvider and + // ResourcePrincipalV1TokenSupplier's + // sessionKeysSupplier must be different. BTW + // FederationSecurityTokenSupplier and + // ResourcePrincipalProvider's sessionKeysSupplier + // must be same. + .build(); + + // Prepare the provider to build the underlying HTTP client. + provider.prepare(new NoSQLHandleConfig(federationEndpoint)); + + String resourcePrincipalTokenPath = resourcePrincipalTokenPathProvider.getPath(); + + return new ResourcePrincipalV1TokenSupplier( + resourcePrincipalTokenEndpoint, + federationEndpoint, + resourcePrincipalTokenPath, + sessionKeyPairSupplier, + provider); + } + + private String createTokenPath(String resourcePrincipalTokenPath, String resourceId) { + return "/" + resourcePrincipalTokenPath + + "/" + resourceId; + } + + protected void createRptPathProvider() { + if (resourcePrincipalTokenPathProvider == null) { + resourcePrincipalTokenPathProvider = new RptPathProvider.DefaultRptPathProvider(); + } + } + } + + protected static SessionKeyPairSupplier getSessionKeySupplierFromPemAndPassphrase( + String resourcePrincipalPrivateKey, + String resourcePrincipalPassphrase) { + SessionKeyPairSupplier sessKeySupplier; + if (resourcePrincipalPrivateKey == null) { + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM + + " environment variable missing"); + } + + if (new File(resourcePrincipalPrivateKey).isAbsolute()) { + if (resourcePrincipalPassphrase != null + && !new File(resourcePrincipalPassphrase).isAbsolute()) { + throw new IllegalArgumentException( + "cannot mix path and constant settings for " + + OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM + " and " + + OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE); + } + logTrace(logger,"Valid file for private key." + + " Creating instance of FileKeyPairSupplier"); + sessKeySupplier = new FileKeyPairSupplier( + resourcePrincipalPrivateKey, resourcePrincipalPassphrase); + } else { + char[] passPhraseChars = null; + if (resourcePrincipalPassphrase != null) { + passPhraseChars = resourcePrincipalPassphrase.toCharArray(); + } + logTrace(logger, "Invalid file for private key, using the" + + " content provided. Creating instance of FixedKeyPairSupplier"); + sessKeySupplier = new FixedKeyPairSupplier(resourcePrincipalPrivateKey, + passPhraseChars); + } + return sessKeySupplier; } } diff --git a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalTokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalTokenSupplier.java deleted file mode 100644 index ea7b7779..00000000 --- a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalTokenSupplier.java +++ /dev/null @@ -1,161 +0,0 @@ -/*- - * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. - * - * Licensed under the Universal Permissive License v 1.0 as shown at - * https://oss.oracle.com/licenses/upl/ - */ - -package oracle.nosql.driver.iam; - -import static oracle.nosql.driver.iam.Utils.logTrace; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.KeyPair; -import java.util.logging.Logger; - -import oracle.nosql.driver.SecurityInfoNotReadyException; -import oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityToken; - -/** - * @hidden - * Internal use only - *

- * The class to supply security token for resource principal - */ -abstract class ResourcePrincipalTokenSupplier { - /* - * The expected minimal lifetime is configured by SignatureProvider, - * the same as the signature cache duration. Security token providers - * should validate token to ensure it has the expected minimal lifetime, - * throw an error otherwise. - */ - protected long minTokenLifetime; - - /** - * Return the security token of resource principal. - */ - abstract String getSecurityToken(); - - /** - * Return the specific claim in security token by given key. - */ - abstract String getStringClaim(String key); - - /** - * Set expected minimal token lifetime. - */ - void setMinTokenLifetime(long lifetimeMS) { - this.minTokenLifetime = lifetimeMS; - } - - /** - * @hidden - * Internal use only - *

- * This interface to supply security token for resource principal from file. - * Reference to the OCI SDK for Java - * com.oracle.bmc.auth.internal.FileBasedResourcePrincipalFederationClient - */ - static class FileSecurityTokenSupplier - extends ResourcePrincipalTokenSupplier { - - private final SessionKeyPairSupplier sessionKeyPairSupplier; - private final String sessionTokenPath; - private volatile SecurityToken securityToken; - private Logger logger; - - FileSecurityTokenSupplier(SessionKeyPairSupplier sessKeyPairSupplier, - String sessionTokenPath, - Logger logger) { - this.sessionKeyPairSupplier = sessKeyPairSupplier; - this.sessionTokenPath = sessionTokenPath; - this.logger = logger; - } - - @Override - public String getSecurityToken() { - return refreshAndGetSecurityToken(); - } - - @Override - public String getStringClaim(String key) { - if (securityToken == null) { - refreshAndGetSecurityToken(); - } - return securityToken.getStringClaim(key); - } - - private synchronized String refreshAndGetSecurityToken() { - logTrace(logger, "Refreshing session keys"); - sessionKeyPairSupplier.refreshKeys(); - - try { - logTrace(logger, "Getting security token from file."); - SecurityToken token = getSecurityTokenFromFile(); - token.validate(minTokenLifetime, logger); - securityToken = token; - return securityToken.getSecurityToken(); - } catch (Exception e) { - throw new SecurityInfoNotReadyException(e.getMessage(), e); - } - } - - SecurityToken getSecurityTokenFromFile() { - KeyPair keyPair = sessionKeyPairSupplier.getKeyPair(); - if (keyPair == null) { - throw new IllegalArgumentException( - "Keypair for session was not provided"); - } - - String sessToken = null; - try { - sessToken = new String( - Files.readAllBytes(Paths.get(sessionTokenPath)), - Charset.defaultCharset()); - } catch (IOException e) { - throw new RuntimeException( - "Unable to read session token from " + sessionTokenPath, e); - } - - return new SecurityToken(sessToken, sessionKeyPairSupplier); - } - } - - /** - * @hidden - * Internal use only - *

- * This interface to supply security token for resource principal from fixed - * String content. - * Reference to the OCI SDK for Java - * com.oracle.bmc.auth.internal.FixedContentResourcePrincipalFederationClient - */ - static class FixedSecurityTokenSupplier - extends ResourcePrincipalTokenSupplier { - - private final SecurityToken securityToken; - private final Logger logger; - - FixedSecurityTokenSupplier(SessionKeyPairSupplier sessionKeySupplier, - String sessionToken, - Logger logger) { - this.securityToken = new SecurityToken(sessionToken, - sessionKeySupplier); - this.logger = logger; - } - - @Override - public String getSecurityToken() { - securityToken.validate(minTokenLifetime, logger); - return securityToken.getSecurityToken(); - } - - @Override - public String getStringClaim(String key) { - return securityToken.getStringClaim(key); - } - } -} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV1TokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV1TokenSupplier.java new file mode 100644 index 00000000..e0e2458d --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV1TokenSupplier.java @@ -0,0 +1,88 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.Utils.logTrace; +import static oracle.nosql.driver.iam.Utils.parseResourcePrincipalTokenResponse; +import static oracle.nosql.driver.iam.Utils.base64EncodeNoChunking; +import static oracle.nosql.driver.util.HttpRequestUtil.HttpResponse; +import static oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityToken; + +import java.security.KeyPair; +import java.security.interfaces.RSAPublicKey; +import java.util.Map; +import java.util.logging.Logger; + +class ResourcePrincipalV1TokenSupplier extends AbstractTokenSupplier { + private static final Logger logger = + Logger.getLogger(ResourcePrincipalV1TokenSupplier.class.getName()); + private final InstancePrincipalsProvider instancePrincipalProvider; + + public ResourcePrincipalV1TokenSupplier( + String resourcePrincipalTokenEndpoint, + String federationEndpoint, + String resourcePrincipalTokenPath, + SessionKeyPairSupplier sessionKeySupplier, + InstancePrincipalsProvider provider + ) { + super( + resourcePrincipalTokenEndpoint, + federationEndpoint, + resourcePrincipalTokenPath, + sessionKeySupplier, + logger + ); + this.instancePrincipalProvider = provider; + } + + @Override + public void close() { + super.close(); + if (instancePrincipalProvider != null) { + instancePrincipalProvider.close(); + } + } + + @Override + protected SecurityToken getSecurityTokenFromServer() { + logTrace(logger, "Getting security token from the auth server"); + + KeyPair keyPair = sessionKeyPairSupplier.getKeyPair(); + if (keyPair == null) { + throw new IllegalArgumentException( + "Keypair for session was not provided"); + } + + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + if (publicKey == null) { + throw new IllegalArgumentException("Public key is not present"); + } + + // Refresh and get instance principal token with Identity + keyId = instancePrincipalProvider.getKeyId(); + privateKeyProvider = new PrivateKeyProvider(instancePrincipalProvider); + + HttpResponse resourcePrincipalTokenResponse = + getResourcePrincipalTokenResponse(null); + + Map resourcePrincipalTokenOutput = + parseResourcePrincipalTokenResponse( + resourcePrincipalTokenResponse.getOutput()); + + String resourcePrincipalToken = + resourcePrincipalTokenOutput.get("resourcePrincipalToken"); + String servicePrincipalSessionToken = + resourcePrincipalTokenOutput.get("servicePrincipalSessionToken"); + + return getResourcePrincipalSessionToken( + resourcePrincipalToken, + servicePrincipalSessionToken, + base64EncodeNoChunking(publicKey) + ); + } +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV2TokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV2TokenSupplier.java new file mode 100644 index 00000000..2180fbcd --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV2TokenSupplier.java @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityToken; +import static oracle.nosql.driver.iam.Utils.logTrace; +import static oracle.nosql.driver.iam.Utils.parseResourcePrincipalTokenResponse; +import static oracle.nosql.driver.iam.Utils.base64EncodeNoChunking; + +import java.security.KeyPair; +import java.security.interfaces.RSAPublicKey; +import java.util.Map; +import java.util.logging.Logger; + +import oracle.nosql.driver.util.HttpRequestUtil; + +class ResourcePrincipalV2TokenSupplier extends AbstractTokenSupplier { + private static final Logger logger = + Logger.getLogger(ResourcePrincipalV2TokenSupplier.class.getName()); + private final KeyPairProvider keyPairprovider; + private final String securityContext; + + public ResourcePrincipalV2TokenSupplier( + String resourcePrincipalTokenEndpoint, + String federationEndpoint, + String resourcePrincipalTokenPath, + SessionKeyPairSupplier sessionKeySupplier, + KeyPairProvider provider, + String securityContext + ) { + super( + resourcePrincipalTokenEndpoint, + federationEndpoint, + resourcePrincipalTokenPath, + sessionKeySupplier, + logger + ); + + this.keyPairprovider = provider; + this.securityContext = securityContext; + } + + @Override + public void close() { + super.close(); + if (keyPairprovider != null) { + keyPairprovider.close(); + } + } + + @Override + protected SecurityToken getSecurityTokenFromServer() { + logTrace(logger, "Getting security token from the auth server"); + + KeyPair keyPair = sessionKeyPairSupplier.getKeyPair(); + if (keyPair == null) { + throw new IllegalArgumentException( + "Keypair for session was not provided"); + } + + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + if (publicKey == null) { + throw new IllegalArgumentException("Public key is not present"); + } + + // get keyId and private key from keyPairSupplier + keyId = keyPairprovider.getKeyId(); + privateKeyProvider = new PrivateKeyProvider(keyPairprovider); + + HttpRequestUtil.HttpResponse resourcePrincipalTokenResponse = + getResourcePrincipalTokenResponse(securityContext); + + Map resourcePrincipalTokenOutput = + parseResourcePrincipalTokenResponse( + resourcePrincipalTokenResponse.getOutput()); + + String resourcePrincipalToken = + resourcePrincipalTokenOutput.get("resourcePrincipalToken"); + String servicePrincipalSessionToken = + resourcePrincipalTokenOutput.get("servicePrincipalSessionToken"); + + return getResourcePrincipalSessionToken( + resourcePrincipalToken, + servicePrincipalSessionToken, + base64EncodeNoChunking(publicKey) + ); + } +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV3Provider.java b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV3Provider.java new file mode 100644 index 00000000..347eafde --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV3Provider.java @@ -0,0 +1,272 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.Utils.isBlank; +import static oracle.nosql.driver.iam.Utils.logTrace; + +import java.io.File; + +import oracle.nosql.driver.Region; + +public class ResourcePrincipalV3Provider + extends ResourcePrincipalProvider { + private static final String OCI_RESOURCE_PRINCIPAL_VERSION_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_VERSION_FOR_LEAF_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT_FOR_LEAF_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT_FOR_LEAF_RESOURCE"; + private static final String RP_VERSION_1_1 = "1.1"; + private static final String OCI_RESOURCE_PRINCIPAL_RESOURCE_ID_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RESOURCE_ID_FOR_LEAF_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_FOR_LEAF_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE_FOR_LEAF_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_RPST_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RPST_FOR_LEAF_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_REGION_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_REGION_FOR_LEAF_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_RPT_URL_FOR_PARENT_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RPT_URL_FOR_PARENT_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT_FOR_PARENT_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT_FOR_PARENT_RESOURCE"; + private static final String OCI_RESOURCE_PRINCIPAL_TENANCY_ID_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_TENANCY_ID_FOR_LEAF_RESOURCE"; + + /** + * Constructor of ResourcePrincipalAuthenticationDetailsProvider. + * + * @param tokenSupplier token supplier implementation. + * @param sessionKeyPairSupplier session key supplier implementation. + * @param region the region + */ + ResourcePrincipalV3Provider(TokenSupplier tokenSupplier, + SessionKeyPairSupplier sessionKeyPairSupplier, + Region region) { + super(tokenSupplier, sessionKeyPairSupplier, region); + } + + /** + * Creates a new ResourcePrincipalProvider. + * + * @return A new builder instance. + */ + public static ResourcePrincipalV3ProviderBuilder builder() { + return new ResourcePrincipalV3ProviderBuilder(); + } + + /** Builder for ResourcePrincipalProviderBuilder. */ + public static class ResourcePrincipalV3ProviderBuilder + extends ResourcePrincipalProviderBuilder { + + /** + * The endpoint that can provide the parent resource principal token. + * + *

Required. + */ + private String resourcePrincipalTokenUrlForParentResource; + + /** The federation endpoint/RPST endpoint for parent resource */ + private String federationEndpointForParentResource; + + /** Configures the resourcePrincipalTokenUrlForParentResource to use. */ + public ResourcePrincipalV3ProviderBuilder + setResourcePrincipalTokenUrlForParentResource( + String resourcePrincipalTokenUrlForParentResource) { + this.resourcePrincipalTokenUrlForParentResource = + resourcePrincipalTokenUrlForParentResource; + return this; + } + + /** Configures the resourcePrincipalFederationUrlForParentResource to use. */ + public ResourcePrincipalV3ProviderBuilder + setFederationEndpointForParentResource(String federationEndpointForParentResource) { + this.federationEndpointForParentResource = federationEndpointForParentResource; + return this; + } + + /** + * Build a new ResourcePrincipalsV3AuthenticationDetailsProvider. + * + * @return A new provider instance. + */ + public ResourcePrincipalV3Provider build() { + final String resourcePrincipalVersionForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_VERSION_FOR_LEAF_RESOURCE); + if (isBlank(resourcePrincipalVersionForLeafResource)) { + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_VERSION_FOR_LEAF_RESOURCE + + " environment variable missing"); + } + ResourcePrincipalProvider leafResourceAuthProvider = null; + switch (resourcePrincipalVersionForLeafResource) { + case RP_VERSION_1_1: + final String resourcePrincipalRptEndpointForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT_FOR_LEAF_RESOURCE); + final String resourcePrincipalRpstEndpointForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT_FOR_LEAF_RESOURCE); + leafResourceAuthProvider = + build_1_1( + resourcePrincipalRptEndpointForLeafResource, + resourcePrincipalRpstEndpointForLeafResource); + break; + case RP_VERSION_2_1: + case RP_VERSION_2_1_1: + final String resourcePrincipalRptEndpointForLeafResourceFor2_1_or_2_1_1 = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_ENDPOINT_FOR_LEAF_RESOURCE); + final String resourcePrincipalRpstEndpointForLeafResourceFor2_1_or_2_1_1 = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT_FOR_LEAF_RESOURCE); + final String resourcePrincipalResourceIdForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_RESOURCE_ID_FOR_LEAF_RESOURCE); + final String resourcePrincipalTenancyIdForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_TENANCY_ID_FOR_LEAF_RESOURCE); + final String resourcePrincipalPrivateKeyForLeafResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_FOR_LEAF_RESOURCE); + final String resourcePrincipalPassphraseForLeafResource = + System.getenv( + OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE_FOR_LEAF_RESOURCE); + leafResourceAuthProvider = + build_2_1_or_2_1_1( + resourcePrincipalRptEndpointForLeafResourceFor2_1_or_2_1_1, + resourcePrincipalRpstEndpointForLeafResourceFor2_1_or_2_1_1, + resourcePrincipalResourceIdForLeafResource, + resourcePrincipalTenancyIdForLeafResource, + resourcePrincipalPrivateKeyForLeafResource, + resourcePrincipalPassphraseForLeafResource, + resourcePrincipalVersionForLeafResource); + break; + case RP_VERSION_2_2: + final String ociResourcePrincipalPrivateKey = + System.getenv(OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_FOR_LEAF_RESOURCE); + final String ociResourcePrincipalPassphrase = + System.getenv( + OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM_PASSPHRASE_FOR_LEAF_RESOURCE); + final String ociResourcePrincipalRpst = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST_FOR_LEAF_RESOURCE); + final String ociResourcePrincipalRegion = + System.getenv(OCI_RESOURCE_PRINCIPAL_REGION_FOR_LEAF_RESOURCE); + leafResourceAuthProvider = + build_2_2_leaf( + ociResourcePrincipalPrivateKey, + ociResourcePrincipalPassphrase, + ociResourcePrincipalRpst, + ociResourcePrincipalRegion); + break; + default: + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_VERSION_FOR_LEAF_RESOURCE + + " has unknown value " + resourcePrincipalVersionForLeafResource); + } + return build(leafResourceAuthProvider); + } + + /** + * Helper method that interprets the runtime environment to build a v2.2-configured leaf + * client + * + * @return ResourcePrincipalProvider + */ + public ResourcePrincipalProvider build_2_2_leaf( + String ResourcePrincipalPrivateKey, + String ResourcePrincipalPassphrase, + String ResourcePrincipalRpst, + String ResourcePrincipalRegion + ) { + sessionKeySupplier = + getSessionKeySupplierFromPemAndPassphrase( + ResourcePrincipalPrivateKey, ResourcePrincipalPassphrase); + + if (ResourcePrincipalRpst == null) { + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_RPST + " environment variable missing"); + } + if (new File(ResourcePrincipalRpst).isAbsolute()) { + logTrace(logger, "Valid file for RPST." + + " Creating instance of FileSecurityTokenSupplier"); + tokenSupplier = new FileSecurityTokenSupplier( + sessionKeySupplier, ResourcePrincipalRpst); + } else { + logTrace(logger, "Loading RPST from content provided. Creating instance of" + + " FixedSecurityTokenSupplier"); + tokenSupplier = new FixedSecurityTokenSupplier( + sessionKeySupplier, ResourcePrincipalRpst); + } + + if (ResourcePrincipalRegion == null) { + throw new IllegalArgumentException( + OCI_RESOURCE_PRINCIPAL_REGION + " environment variable missing"); + } + region = Region.fromRegionId(ResourcePrincipalRegion); + return new ResourcePrincipalProvider(tokenSupplier, sessionKeySupplier, region); + } + + /** + * Builds a new instance of ResourcePrincipalsV3Provider + * + * @param leafResourceAuthProvider instance of + * ResourcePrincipalProvider for leaf resource + */ + public ResourcePrincipalV3Provider build( + ResourcePrincipalProvider leafResourceAuthProvider) { + final String ociResourcePrincipalRptUrlForParentResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_URL_FOR_PARENT_RESOURCE); + + resourcePrincipalTokenUrlForParentResource = + resourcePrincipalTokenUrlForParentResource != null + ? resourcePrincipalTokenUrlForParentResource + : ociResourcePrincipalRptUrlForParentResource; + + if (resourcePrincipalTokenUrlForParentResource == null) { + throw new NullPointerException( + "resourcePrincipalTokenUrlForParentResource must not be null"); + } + + final String ociResourcePrincipalRpstEndpointForParentResource = + System.getenv(OCI_RESOURCE_PRINCIPAL_RPST_ENDPOINT_FOR_PARENT_RESOURCE); + + federationEndpointForParentResource = + federationEndpointForParentResource != null + ? federationEndpointForParentResource + : ociResourcePrincipalRpstEndpointForParentResource; + + if (federationEndpointForParentResource == null) { + federationEndpointForParentResource = autoDetectEndpointUsingMetadataUrl(); + } + + TokenSupplier federationClientForParentResource = + createFederationClientForParentResource(leafResourceAuthProvider); + + return new ResourcePrincipalV3Provider( + federationClientForParentResource, sessionKeySupplier, region); + } + + private TokenSupplier createFederationClientForParentResource( + ResourcePrincipalProvider leafResourceAuthProvider) { + return new ResourcePrincipalV3TokenSupplier( + resourcePrincipalTokenUrlForParentResource, + federationEndpointForParentResource, + sessionKeySupplier, + leafResourceAuthProvider); + } + + @Override + protected void createRptPathProvider() { + if(resourcePrincipalTokenEndpoint == null) { + throw new NullPointerException( + "resourcePrincipalTokenEndpoint for leaf-resource must not be null"); + } + if (resourcePrincipalTokenPathProvider == null) { + resourcePrincipalTokenPathProvider = new RptPathProvider.DefaultLeafRptPathProvider(); + } + } + } + + +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV3TokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV3TokenSupplier.java new file mode 100644 index 00000000..6e45bdaf --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/ResourcePrincipalV3TokenSupplier.java @@ -0,0 +1,168 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.Utils.logTrace; +import static oracle.nosql.driver.iam.Utils.parseResourcePrincipalTokenResponse; +import static oracle.nosql.driver.iam.Utils.base64EncodeNoChunking; +import static oracle.nosql.driver.iam.Utils.isBlank; +import static oracle.nosql.driver.util.HttpRequestUtil.HttpResponse; +import static oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityToken; + +import java.net.URI; +import java.security.KeyPair; +import java.security.interfaces.RSAPublicKey; +import java.util.Map; +import java.util.logging.Logger; + +import io.netty.handler.codec.http.HttpHeaders; + +/** + * This class gets a security token from the auth service by fetching the RPST1 and then passing + * along the RPST1 to get RPT2 and further get security token RPST2 from the auth service, this + * nested fetching of security token continues for 10 levels or when the opc-parent-url header in + * the rpt response is the same as the rpt endpoint + */ +class ResourcePrincipalV3TokenSupplier extends AbstractTokenSupplier { + private static final Logger logger = + Logger.getLogger(ResourcePrincipalV3TokenSupplier.class.getName()); + private final String resourcePrincipalTokenUrl; + private final ResourcePrincipalProvider leafAuthProvider; + private final String OPC_PARENT_RPT_URL_HEADER = "opc-parent-rpt-url"; + + /** + * Constructor of ResourcePrincipalsTokenSupplier. + * + * @param resourcePrincipalTokenUrl the direct url that can provide the resource principal + * token. + * @param resourcePrincipalSessionTokenEndpoint the endpoint that can provide the resource + * principal session token. + * @param sessionKeySupplier the session key supplier. + * @param leafAuthProvider the auth provider for leaf resource + */ + public ResourcePrincipalV3TokenSupplier( + String resourcePrincipalTokenUrl, + String resourcePrincipalSessionTokenEndpoint, + SessionKeyPairSupplier sessionKeySupplier, + ResourcePrincipalProvider leafAuthProvider) { + super( + resourcePrincipalTokenUrl, + resourcePrincipalSessionTokenEndpoint, + sessionKeySupplier, + logger); + + this.resourcePrincipalTokenUrl = resourcePrincipalTokenUrl; + this.leafAuthProvider = leafAuthProvider; + } + + @Override + public synchronized void close() { + super.close(); + if (leafAuthProvider != null) { + leafAuthProvider.close(); + } + } + + @Override + protected SecurityToken getSecurityTokenFromServer() { + logTrace(logger, "Getting/Refreshing RPST leaf from the auth server"); + + // Refresh and get resource principal token from identity + keyId = leafAuthProvider.getKeyId(); + privateKeyProvider = new PrivateKeyProvider(leafAuthProvider); + + // use the refreshed session keys + KeyPair keyPair = sessionKeyPairSupplier.getKeyPair(); + if (keyPair == null) { + throw new IllegalArgumentException( + "Keypair for session was not provided"); + } + + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + if (publicKey == null) { + throw new IllegalArgumentException("Public key is not present"); + } + + return getSecurityTokenFromServerInner( + this.resourcePrincipalTokenUrl, + publicKey, + 1); + } + + protected SecurityToken getSecurityTokenFromServerInner( + String lastResourcePrincipalTokenUrl, + RSAPublicKey publicKey, + int depth + ) { + final HttpResponse resourcePrincipalTokenResponse = + getResourcePrincipalTokenResponse(null); + final HttpHeaders headers = resourcePrincipalTokenResponse.getHeaders(); + + // check for the parent rpt url in response headers + String opcParentUrlHeader = null; + if (headers != null && !headers.isEmpty()) { + opcParentUrlHeader = + headers.get(OPC_PARENT_RPT_URL_HEADER) != null + ? headers.get(OPC_PARENT_RPT_URL_HEADER).trim() + : null; + } + + final Map resourcePrincipalTokenResponseBody = + parseResourcePrincipalTokenResponse( + resourcePrincipalTokenResponse.getOutput()); + + String resourcePrincipalToken = + resourcePrincipalTokenResponseBody.get("resourcePrincipalToken"); + String servicePrincipalSessionToken = + resourcePrincipalTokenResponseBody.get("servicePrincipalSessionToken"); + + final SecurityToken securityToken = getResourcePrincipalSessionToken( + resourcePrincipalToken, + servicePrincipalSessionToken, + base64EncodeNoChunking(publicKey) + ); + + // if depth is more than 10, return the security token obtained last + if (depth > 9) return securityToken; + + // get the opc-parent-rpt-url header and check if matches the last one + // if the opcParentUrlHeader matches the last rpt url, return the security token last + // obtained + if (isBlank(opcParentUrlHeader) + || (!isBlank(opcParentUrlHeader) + && opcParentUrlHeader.equalsIgnoreCase(lastResourcePrincipalTokenUrl))) { + return securityToken; + } + + ResourcePrincipalProvider tempAuthProvider = new ResourcePrincipalProvider( + new FixedSecurityTokenSupplier( + sessionKeyPairSupplier, securityToken.getSecurityToken()), + sessionKeyPairSupplier, + leafAuthProvider.getRegion()); + + // Update resource principal token and private key using newly formed auth provider. + keyId = tempAuthProvider.getKeyId(); + privateKeyProvider = new PrivateKeyProvider(tempAuthProvider); + + // Free the client to release resources. We neither close the temp provider + // as it's a FixedSecurityTokenSupplier resource principal provider and + // doesn't contain a underlying http client, nor we close federation client + // as the same client can be reused in all the iterations + resourcePrincipalTokenClient.shutdown(); + + resourcePrincipalTokenURI = URI.create(opcParentUrlHeader); + resourcePrincipalTokenClient = buildHttpClient(resourcePrincipalTokenURI, + "resourcePrincipalTokenClient", logger); + + // recurse with the new value of the rpt url + return getSecurityTokenFromServerInner( + opcParentUrlHeader, + publicKey, + depth + 1); + } +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/RptPathProvider.java b/driver/src/main/java/oracle/nosql/driver/iam/RptPathProvider.java new file mode 100644 index 00000000..763d93e6 --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/RptPathProvider.java @@ -0,0 +1,185 @@ +/*- + * Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * https://oss.oracle.com/licenses/upl/ + */ + +package oracle.nosql.driver.iam; + +import static oracle.nosql.driver.iam.Utils.logTrace; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * This path provider makes sure the behavior happens with the correct fallback. + * + *

For the path, Use the contents of the OCI_RESOURCE_PRINCIPAL_RPT_PATH environment variable, if + * set. Otherwise, use the current path: "/20180711/resourcePrincipalToken/{id}" + * + *

For the resource id, Use the contents of the OCI_RESOURCE_PRINCIPAL_RPT_ID environment + * variable, if set. Otherwise, use IMDS to get the instance id + */ +public abstract class RptPathProvider { + private static final Logger logger = Logger.getLogger(RptPathProvider.class.getName()); + + private static final String IMDS_PATH_TEMPLATE + = "/20180711/resourcePrincipalToken/{id}"; + private static final int timeoutMs = 5_000; + private final String pathTemplate; + + public RptPathProvider(String pathTemplate) { + this.pathTemplate = pathTemplate; + } + + public String getPath() { + Map replacements = getReplacements(); + String path = Utils.replace(pathTemplate, replacements, "{", "}"); + logTrace(logger,"Using path " + path); + return path; + } + + protected abstract Map getReplacements(); + + public static Map buildEnvironmentRptPathProviderReplacements( + String rptId) { + if (rptId != null) { + Map replacements = new HashMap<>(); + replacements.put("id", rptId); + return Collections.unmodifiableMap(replacements); + } else { + return null; + } + } + + public static Map buildImdsRptPathProviderReplacements() { + // Get instance Id from metadata service + Map replacements = new HashMap<>(); + replacements.put("id", InstanceMetadataHelper. + fetchMetadata(timeoutMs, logger).getId()); + return Collections.unmodifiableMap(replacements); + } + + /** + * This path provider makes sure the behavior happens with the correct fallback. + * + *

For the path, Use the contents of the OCI_RESOURCE_PRINCIPAL_RPT_PATH environment variable, if + * set. Otherwise, use the current path: "/20180711/resourcePrincipalToken/{id}" + * + *

For the resource id, Use the contents of the OCI_RESOURCE_PRINCIPAL_RPT_ID environment + * variable, if set. Otherwise, use IMDS to get the instance id + * + *

This path provider is used when the caller doesn't provide a specific path provider to the + * resource principals signer + */ + public static class DefaultRptPathProvider extends RptPathProvider { + static final String OCI_RESOURCE_PRINCIPAL_RPT_PATH + = "OCI_RESOURCE_PRINCIPAL_RPT_PATH"; + static final String OCI_RESOURCE_PRINCIPAL_RPT_ID + = "OCI_RESOURCE_PRINCIPAL_RPT_ID"; + private final Map replacements; + + public DefaultRptPathProvider() { + super(getPathTemplate()); + replacements = buildReplacements(); + } + + @Override + protected Map getReplacements() { + return replacements; + } + + private static String getPathTemplate() { + String pathTemplate = System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_PATH); + if (pathTemplate == null) { + logTrace(logger, + "Unable to get path template from " + + OCI_RESOURCE_PRINCIPAL_RPT_PATH + + " env variable, using IMDS template" + ); + pathTemplate = IMDS_PATH_TEMPLATE; + } + logTrace(logger, "The path template is " + pathTemplate); + return pathTemplate; + } + + private Map buildReplacements() { + String rptId = System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_ID); + Map replacementMap = buildEnvironmentRptPathProviderReplacements(rptId); + if (replacementMap == null) { + logTrace(logger, + "Unable to get replacements from " + + OCI_RESOURCE_PRINCIPAL_RPT_ID + + "env variable, getting replacements from IMDS" + ); + replacementMap = buildImdsRptPathProviderReplacements(); + } + logTrace(logger, "The replacement map is " + replacementMap); + return replacementMap; + } + } + + /** + * This path provider makes sure the behavior happens with the correct fallback. + * + *

For the path, Use the contents of the OCI_RESOURCE_PRINCIPAL_RPT_PATH_FOR_LEAF_RESOURCE + * environment variable, if set. Otherwise, use the current path: + * "/20180711/resourcePrincipalToken/{id}" + * + *

For the resource id, Use the contents of the OCI_RESOURCE_PRINCIPAL_RPT_ID_FOR_LEAF_RESOURCE + * environment variable, if set. Otherwise, use IMDS to get the instance id + * + *

This path provider is used when the caller doesn't provide a specific path provider to the + * resource principals signer + */ + public static class DefaultLeafRptPathProvider extends RptPathProvider { + + static final String OCI_RESOURCE_PRINCIPAL_RPT_PATH_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RPT_PATH_FOR_LEAF_RESOURCE"; + static final String OCI_RESOURCE_PRINCIPAL_RPT_ID_FOR_LEAF_RESOURCE = + "OCI_RESOURCE_PRINCIPAL_RPT_ID_FOR_LEAF_RESOURCE"; + private final Map replacements; + + public DefaultLeafRptPathProvider() { + super(getPathTemplate()); + replacements = buildReplacements(); + } + + @Override + protected Map getReplacements() { + return replacements; + } + + private static String getPathTemplate() { + String pathTemplate = System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_PATH_FOR_LEAF_RESOURCE); + if (pathTemplate == null) { + logTrace(logger, + "Unable to get path template from " + + OCI_RESOURCE_PRINCIPAL_RPT_PATH_FOR_LEAF_RESOURCE + + " env variable, using IMDS template" + ); + pathTemplate = IMDS_PATH_TEMPLATE; + } + logTrace(logger, "The path template is " + pathTemplate); + return pathTemplate; + } + + private Map buildReplacements() { + String rptId = System.getenv(OCI_RESOURCE_PRINCIPAL_RPT_ID_FOR_LEAF_RESOURCE); + Map replacementMap = buildEnvironmentRptPathProviderReplacements(rptId); + if (replacementMap == null) { + logTrace(logger, + "Unable to get replacements from " + + OCI_RESOURCE_PRINCIPAL_RPT_ID_FOR_LEAF_RESOURCE + + "env variable, getting replacements from IMDS" + ); + replacementMap = buildImdsRptPathProviderReplacements(); + } + logTrace(logger, "The replacement map is " + replacementMap); + return replacementMap; + } + } +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/SecurityTokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/SecurityTokenSupplier.java index f861ff9d..2d9a979b 100644 --- a/driver/src/main/java/oracle/nosql/driver/iam/SecurityTokenSupplier.java +++ b/driver/src/main/java/oracle/nosql/driver/iam/SecurityTokenSupplier.java @@ -41,7 +41,7 @@ * Reference to the OCI SDK for Java * com.oracle.bmc.auth.internal X509FederationClient */ -class SecurityTokenSupplier { +class SecurityTokenSupplier implements TokenSupplier { private String tenantId; private int timeoutMS; private URI federationURL; @@ -51,6 +51,7 @@ class SecurityTokenSupplier { private final SessionKeyPairSupplier keyPairSupplier; private final String purpose; private long minTokenLifetime; + private volatile SecurityToken securityToken; private final Logger logger; SecurityTokenSupplier(String federationEndpoint, @@ -77,21 +78,33 @@ class SecurityTokenSupplier { this.logger = logger; } - String getSecurityToken() { + @Override + public String getSecurityToken() { return refreshAndGetTokenInternal(); } - void setMinTokenLifetime(long lifetimeMS) { + @Override + public String getStringClaim(String key) { + if (securityToken == null) { + refreshAndGetTokenInternal(); + } + return securityToken.getStringClaim(key); + } + + @Override + public void setMinTokenLifetime(long lifetimeMS) { this.minTokenLifetime = lifetimeMS; } - void close() { + @Override + public void close() { if (federationClient != null) { federationClient.shutdown(); } } - synchronized void prepare(NoSQLHandleConfig config) { + @Override + public synchronized void prepare(NoSQLHandleConfig config) { if (federationClient == null) { federationClient = buildHttpClient( federationURL, @@ -203,13 +216,14 @@ private String getSecurityTokenFromIAM() { } } - String securityToken = getSecurityTokenInternal( + String token = getSecurityTokenInternal( Utils.base64EncodeNoChunking(publicKey), Utils.base64EncodeNoChunking(certificateAndKeyPair), intermediateStrings); - SecurityToken st = new SecurityToken(securityToken, keyPairSupplier); + SecurityToken st = new SecurityToken(token, keyPairSupplier); st.validate(minTokenLifetime, logger); + securityToken = st; return st.getSecurityToken(); } catch (Exception e) { throw new SecurityInfoNotReadyException(e.getMessage(), e); @@ -220,8 +234,9 @@ private String getSecurityTokenInternal(String publicKey, String leafCertificate, Set interCerts) { - String body = FederationRequestHelper.getFederationRequestBody( - publicKey, leafCertificate, interCerts, purpose); + String body = + FederationRequestHelper.getInstancePrincipalSessionTokenRequestBody( + publicKey, leafCertificate, interCerts, purpose); logTrace(logger, "Federation request body " + body); return FederationRequestHelper.getSecurityToken( federationClient, federationURL, timeoutMS, tenantId, diff --git a/driver/src/main/java/oracle/nosql/driver/iam/SignatureProvider.java b/driver/src/main/java/oracle/nosql/driver/iam/SignatureProvider.java index bc2f76ec..ec1b71b6 100644 --- a/driver/src/main/java/oracle/nosql/driver/iam/SignatureProvider.java +++ b/driver/src/main/java/oracle/nosql/driver/iam/SignatureProvider.java @@ -650,7 +650,7 @@ public static SignatureProvider createWithResourcePrincipal() { */ public static SignatureProvider createWithResourcePrincipal(Logger logger) { SignatureProvider provider = new SignatureProvider( - ResourcePrincipalProvider.build(logger)); + ResourcePrincipalProvider.builder().build()); provider.setLogger(logger); return provider; } diff --git a/driver/src/main/java/oracle/nosql/driver/iam/TokenSupplier.java b/driver/src/main/java/oracle/nosql/driver/iam/TokenSupplier.java new file mode 100644 index 00000000..63133dba --- /dev/null +++ b/driver/src/main/java/oracle/nosql/driver/iam/TokenSupplier.java @@ -0,0 +1,36 @@ +package oracle.nosql.driver.iam; + +import oracle.nosql.driver.NoSQLHandleConfig; + +/** + * Defines a basic interface for token suppliers that provides a security + * token for authentication. + */ +interface TokenSupplier { + /** + * Gets a security token from the federation endpoint. + * + * @return A security token that can be used to authenticate requests. + */ + String getSecurityToken(); + + /** + * Builds HTTP client using config. + */ + void prepare(NoSQLHandleConfig config); + + /** + * Return the specific claim in security token by given key. + */ + String getStringClaim(String key); + + /** + * Set expected minimal token lifetime. + */ + void setMinTokenLifetime(long lifetimeMS); + + /** + * Cleanup the resources used by the token supplier + */ + void close(); +} diff --git a/driver/src/main/java/oracle/nosql/driver/iam/Utils.java b/driver/src/main/java/oracle/nosql/driver/iam/Utils.java index 2d0f442f..d3425a6e 100644 --- a/driver/src/main/java/oracle/nosql/driver/iam/Utils.java +++ b/driver/src/main/java/oracle/nosql/driver/iam/Utils.java @@ -14,6 +14,10 @@ import java.io.InputStreamReader; import java.io.StringWriter; import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.MessageDigest; @@ -94,6 +98,11 @@ class Utils { SignatureProvider.ResourcePrincipalClaimKeys.COMPARTMENT_ID_CLAIM_KEY, SignatureProvider.ResourcePrincipalClaimKeys.TENANT_ID_CLAIM_KEY}; + /* fields in Resource Principal Token JSON */ + private static final String[] RESOURCE_PRINCIPAL_TOKEN_FIELDS = { + "resourcePrincipalToken", + "servicePrincipalSessionToken"}; + static String getIAMURL(String regionIdOrCode) { Region region = Region.fromRegionIdOrCode(regionIdOrCode); if (region != null) { @@ -204,6 +213,25 @@ static byte[] toByteArray(RSAPrivateKey key) { return Pem.encoder().with(Pem.Format.LEGACY).encode(key); } + /** + * Convert the input stream to a byte array. + * + * @param inputStream input stream + * @return byte array + */ + static byte[] toByteArray(InputStream inputStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (!(inputStream instanceof BufferedInputStream)) { + inputStream = new BufferedInputStream(inputStream); + } + byte[] buf = new byte[4096]; + int bytesRead = 0; + while (-1 != (bytesRead = inputStream.read(buf))) { + baos.write(buf, 0, bytesRead); + } + return baos.toByteArray(); + } + /** * Base64 encodes a public key with no chunking. * @param publicKey The public key @@ -393,6 +421,28 @@ static String computeBodySHA256(byte[] body) { } } + static byte[] stringToUtf8Bytes(String input) { + if (input == null) { + return new byte[0]; // Return empty byte array if input is null + } + CharBuffer charBuf = CharBuffer.wrap(input); + ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(charBuf); + byte[] bytes = new byte[byteBuf.remaining()]; + byteBuf.get(bytes); + return bytes; + } + + /* + * Return true if this string is either null or just whitespace. + */ + static boolean isBlank(String s) { + return s == null || s.trim().isEmpty(); + } + + static boolean isNotBlank(String s) { + return !isBlank(s); + } + static JsonParser createParser(String json) throws IOException { @@ -441,6 +491,29 @@ static String findField(String json, JsonParser parser, String... name) return null; } + /** + * Replace the placeholders in the template with the values in the replacement mapping. + * + * @param template template string + * @param replacements map from key to replacement value + * @param prefix prefix of the placeholder + * @param suffix suffix of the placeholder + * @return replaced string + */ + static String replace( + String template, Map replacements, String prefix, String suffix) { + String result = template; + for (Map.Entry e : replacements.entrySet()) { + result = + result.replaceAll( + Pattern.quote(prefix) + + Pattern.quote(e.getKey()) + + Pattern.quote(suffix), + e.getValue()); + } + return result; + } + /* * Parse JSON response that only has one field token. * { "token": "...."} @@ -467,6 +540,39 @@ static String parseTokenResponse(String response) { } } + /* + * Parse JSON Resource Principal Token response. + * { + * "resourcePrincipalToken": "....", + * "servicePrincipalSessionToken": "...." + * } + */ + static Map parseResourcePrincipalTokenResponse(String response) { + try { + Map results = new HashMap<>(); + JsonParser parser = createParser(response); + if (parser.getCurrentToken() == null) { + parser.nextToken(); + } + while (parser.getCurrentToken() != null) { + String field = findField(response, parser, RESOURCE_PRINCIPAL_TOKEN_FIELDS); + if (field != null) { + parser.nextToken(); + results.put(field, parser.getText()); + } + } + if(results.isEmpty()) { + throw new IllegalStateException( + "Unable to find resource principal tokens in " + response); + } + return results; + } catch (IOException ioe) { + throw new IllegalStateException( + "Error parsing resource principal tokens " + response + + " " + ioe.getMessage()); + } + } + /* * Parse security token JSON response get fields expiration time, * modulus and public exponent of JWK, only used for security token @@ -527,6 +633,29 @@ private static void parse(String json, } } + static String convertMapToJson(Map map) { + StringWriter writer = new StringWriter(); + + try { + JsonFactory factory = new JsonFactory(); + JsonGenerator generator = factory.createGenerator(writer); + + generator.writeStartObject(); + + for (Map.Entry entry : map.entrySet()) { + generator.writeStringField(entry.getKey(), entry.getValue()); + } + + generator.writeEndObject(); + generator.close(); + + return writer.toString(); + } catch (IOException ioe) { + throw new IllegalStateException( + "Error converting Map to JSON "+ ioe.getMessage()); + } + } + private static String[] splitJWT(String jwt) { final int dot1 = jwt.indexOf("."); final int dot2 = jwt.indexOf(".", dot1 + 1); diff --git a/driver/src/main/java/oracle/nosql/driver/util/HttpRequestUtil.java b/driver/src/main/java/oracle/nosql/driver/util/HttpRequestUtil.java index d0ea97bb..e6969b96 100644 --- a/driver/src/main/java/oracle/nosql/driver/util/HttpRequestUtil.java +++ b/driver/src/main/java/oracle/nosql/driver/util/HttpRequestUtil.java @@ -243,7 +243,8 @@ private static HttpResponse doRequest(HttpClient httpClient, throw new IllegalStateException("Invalid null response"); } res = processResponse(status.code(), - responseHandler.getContent()); + responseHandler.getContent(), + responseHandler.getHeaders()); /* * Retry upon status code larger than 500, in general, @@ -374,12 +375,14 @@ private static void delay() { * A simple response processing method, just return response content * in String with its status code. */ - private static HttpResponse processResponse(int status, ByteBuf content) { + private static HttpResponse processResponse(int status, + ByteBuf content, + HttpHeaders headers) { String output = null; if (content != null) { output = content.toString(utf8); } - return new HttpResponse(status, output); + return new HttpResponse(status, output, headers); } /** @@ -388,10 +391,12 @@ private static HttpResponse processResponse(int status, ByteBuf content) { public static class HttpResponse { private final int statusCode; private final String output; + private final HttpHeaders headers; - public HttpResponse(int statusCode, String output) { + public HttpResponse(int statusCode, String output, HttpHeaders headers) { this.statusCode = statusCode; this.output = output; + this.headers = headers; } public int getStatusCode() { @@ -402,10 +407,15 @@ public String getOutput() { return output; } + public HttpHeaders getHeaders() { + return headers; + } + @Override public String toString() { return "HttpResponse [statusCode=" + statusCode + "," + - "output=" + output + "]"; + "output=" + output + "," + "headers=" + + (headers == null ? "null" : headers.toString()) + "]"; } } } diff --git a/driver/src/test/java/oracle/nosql/driver/iam/ResourcePrincipalProviderTest.java b/driver/src/test/java/oracle/nosql/driver/iam/ResourcePrincipalProviderTest.java index 5b5ac5fb..62252272 100644 --- a/driver/src/test/java/oracle/nosql/driver/iam/ResourcePrincipalProviderTest.java +++ b/driver/src/test/java/oracle/nosql/driver/iam/ResourcePrincipalProviderTest.java @@ -25,8 +25,6 @@ import oracle.nosql.driver.DriverTestBase; import oracle.nosql.driver.NoSQLHandleConfig; import oracle.nosql.driver.Region; -import oracle.nosql.driver.iam.ResourcePrincipalTokenSupplier.FileSecurityTokenSupplier; -import oracle.nosql.driver.iam.ResourcePrincipalTokenSupplier.FixedSecurityTokenSupplier; import oracle.nosql.driver.iam.SecurityTokenSupplier.SecurityToken; import oracle.nosql.driver.iam.SessionKeyPairSupplier.FileKeyPairSupplier; import oracle.nosql.driver.iam.SessionKeyPairSupplier.FixedKeyPairSupplier; @@ -35,28 +33,28 @@ public class ResourcePrincipalProviderTest extends DriverTestBase { private static String TOKEN = - "{" + - "\"sub\": \"ocid1.resource.oc1.phx.resource\"," + - "\"opc-certtype\": \"resource\"," + - "\"iss\": \"authService.oracle.com\"," + - "\"res_compartment\": \"compartmentId\"," + - "\"res_tenant\": \"tenantId\"," + - "\"fprint\": \"fingerprint\"," + - "\"ptype\": \"instance\"," + - "\"aud\": \"oci\"," + - "\"opc-tag\": \"V1,ocid1.dynamicgroup.oc1..dgroup\"," + - "\"ttype\": \"x509\"," + - "\"opc-instance\": \"ocid1.instance.oc1.phx.instance\"," + - "\"exp\": %s," + - "\"opc-compartment\": \"TestTenant\"," + - "\"iat\": 19888762000," + - "\"jti\": \"jti\"," + - "\"tenant\": \"TestTenant\"," + - "\"jwk\": \"{\\\"kid\\\":\\\"kid\\\"," + - "\\\"n\\\":\\\"%s\\\",\\\"e\\\":\\\"%s\\\"," + - "\\\"kty\\\":\\\"RSA\\\",\\\"alg\\\":\\\"RS256\\\"," + - "\\\"use\\\":\\\"sig\\\"}\"," + - "\"opc-tenant\": \"TestTenant\"}"; + "{" + + "\"sub\": \"ocid1.resource.oc1.phx.resource\"," + + "\"opc-certtype\": \"resource\"," + + "\"iss\": \"authService.oracle.com\"," + + "\"res_compartment\": \"compartmentId\"," + + "\"res_tenant\": \"tenantId\"," + + "\"fprint\": \"fingerprint\"," + + "\"ptype\": \"instance\"," + + "\"aud\": \"oci\"," + + "\"opc-tag\": \"V1,ocid1.dynamicgroup.oc1..dgroup\"," + + "\"ttype\": \"x509\"," + + "\"opc-instance\": \"ocid1.instance.oc1.phx.instance\"," + + "\"exp\": %s," + + "\"opc-compartment\": \"TestTenant\"," + + "\"iat\": 19888762000," + + "\"jti\": \"jti\"," + + "\"tenant\": \"TestTenant\"," + + "\"jwk\": \"{\\\"kid\\\":\\\"kid\\\"," + + "\\\"n\\\":\\\"%s\\\",\\\"e\\\":\\\"%s\\\"," + + "\\\"kty\\\":\\\"RSA\\\",\\\"alg\\\":\\\"RS256\\\"," + + "\\\"use\\\":\\\"sig\\\"}\"," + + "\"opc-tenant\": \"TestTenant\"}"; private static KeyPairInfo keypair; @BeforeClass @@ -76,7 +74,7 @@ public void tearDown() { @Test public void testKeyPairSupplier() - throws Exception { + throws Exception { String key = generatePrivateKeyFile("key.pem", null); SessionKeyPairSupplier supplier = new FileKeyPairSupplier(key, null); @@ -86,9 +84,9 @@ public void testKeyPairSupplier() Files.write(passphraseFile.toPath(), "123456".getBytes()); String encKey = generatePrivateKeyFile("enckey1.pem", - "123456".toCharArray()); + "123456".toCharArray()); supplier = new FileKeyPairSupplier(encKey, - passphraseFile.getAbsolutePath()); + passphraseFile.getAbsolutePath()); assertNotNull(supplier.getKeyPair()); try { @@ -100,8 +98,8 @@ public void testKeyPairSupplier() try { supplier = new FileKeyPairSupplier( - generatePrivateKeyFile("enckey2.pem", "123456".toCharArray()), - "non-existent-passphrase"); + generatePrivateKeyFile("enckey2.pem", "123456".toCharArray()), + "non-existent-passphrase"); fail("expected"); } catch (IllegalStateException ise) { assertThat(ise.getMessage(), "Unable to read"); @@ -111,8 +109,8 @@ public void testKeyPairSupplier() Files.write(passphraseFile.toPath(), "1234".getBytes()); try { supplier = new FileKeyPairSupplier( - generatePrivateKeyFile("enckey3.pem", "123456".toCharArray()), - passphraseFile.getAbsolutePath()); + generatePrivateKeyFile("enckey3.pem", "123456".toCharArray()), + passphraseFile.getAbsolutePath()); fail("expected"); } catch (IllegalArgumentException iae) { assertThat(iae.getMessage(), "passphrase is incorrect"); @@ -137,56 +135,55 @@ public void testKeyPairSupplier() @Test public void testResourcePrincipalTokenSupplier() - throws Exception { + throws Exception { String token = securityToken(TOKEN, keypair.getPublicKey()); Path keyFile = Files.write(Paths.get(getTestDir(), "key.pem"), - keypair.getKey().getBytes(), - StandardOpenOption.CREATE); + keypair.getKey().getBytes(), + StandardOpenOption.CREATE); SessionKeyPairSupplier keySupplier = new FileKeyPairSupplier( - keyFile.toAbsolutePath().toString(), null); - ResourcePrincipalTokenSupplier supplier = - new FixedSecurityTokenSupplier(keySupplier, token, null); + keyFile.toAbsolutePath().toString(), null); + FixedSecurityTokenSupplier supplier = + new FixedSecurityTokenSupplier(keySupplier, token); assertEquals(supplier.getSecurityToken(), token); assertEquals(supplier.getStringClaim( - ResourcePrincipalClaimKeys.COMPARTMENT_ID_CLAIM_KEY), - "compartmentId"); + ResourcePrincipalClaimKeys.COMPARTMENT_ID_CLAIM_KEY), + "compartmentId"); assertEquals(supplier.getStringClaim( - ResourcePrincipalClaimKeys.TENANT_ID_CLAIM_KEY), - "tenantId"); + ResourcePrincipalClaimKeys.TENANT_ID_CLAIM_KEY), + "tenantId"); File tokenFile = new File(getTestDir(), "token"); Files.write(tokenFile.toPath(), token.getBytes()); FileSecurityTokenSupplier fileSupplier = - new FileSecurityTokenSupplier(keySupplier, - tokenFile.getAbsolutePath(), - null); + new FileSecurityTokenSupplier(keySupplier, + tokenFile.getAbsolutePath()); SecurityToken st = fileSupplier.getSecurityTokenFromFile(); assertEquals(st.getSecurityToken(), token); assertEquals(st.getStringClaim( - ResourcePrincipalClaimKeys.COMPARTMENT_ID_CLAIM_KEY), - "compartmentId"); + ResourcePrincipalClaimKeys.COMPARTMENT_ID_CLAIM_KEY), + "compartmentId"); assertEquals(st.getStringClaim( - ResourcePrincipalClaimKeys.TENANT_ID_CLAIM_KEY), - "tenantId"); + ResourcePrincipalClaimKeys.TENANT_ID_CLAIM_KEY), + "tenantId"); } @Test public void testResourcePrincipal() - throws Exception { + throws Exception { String token = securityToken(TOKEN, keypair.getPublicKey()); Path keyFile = Files.write(Paths.get(getTestDir(), "key.pem"), - keypair.getKey().getBytes(), - StandardOpenOption.CREATE); + keypair.getKey().getBytes(), + StandardOpenOption.CREATE); SessionKeyPairSupplier keySupplier = new FileKeyPairSupplier( - keyFile.toAbsolutePath().toString(), null); - ResourcePrincipalTokenSupplier tokenSupplier = - new FixedSecurityTokenSupplier(keySupplier, token, null); + keyFile.toAbsolutePath().toString(), null); + FixedSecurityTokenSupplier tokenSupplier = + new FixedSecurityTokenSupplier(keySupplier, token); ResourcePrincipalProvider rpProvider = - new ResourcePrincipalProvider(tokenSupplier, - keySupplier, - Region.US_ASHBURN_1); + new ResourcePrincipalProvider(tokenSupplier, + keySupplier, + Region.US_ASHBURN_1); assertNotNull(rpProvider.getKeyId()); @@ -201,10 +198,10 @@ public void testResourcePrincipal() assertNotNull(signature); assertEquals(provider.getResourcePrincipalClaim( - ResourcePrincipalClaimKeys.COMPARTMENT_ID_CLAIM_KEY), - "compartmentId"); + ResourcePrincipalClaimKeys.COMPARTMENT_ID_CLAIM_KEY), + "compartmentId"); assertEquals(provider.getResourcePrincipalClaim( - ResourcePrincipalClaimKeys.TENANT_ID_CLAIM_KEY), - "tenantId"); + ResourcePrincipalClaimKeys.TENANT_ID_CLAIM_KEY), + "tenantId"); } }