Skip to content

Commit

Permalink
support proxy auth with gcp http transport (#2261)
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Avetisyan <[email protected]>
Co-authored-by: Henry Avetisyan <[email protected]>
  • Loading branch information
havetisyan and havetisyan authored Jul 29, 2023
1 parent 0da4d3c commit 93fdd3d
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package com.yahoo.athenz.creds.gcp;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.http.apache.v2.ApacheHttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.gson.GsonFactory;
import com.google.auth.http.HttpTransportFactory;
Expand All @@ -26,57 +26,79 @@
import com.oath.auth.Utils;
import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.athenz.zts.ZTSClient;
import org.apache.http.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.*;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestExecutor;

import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URLEncoder;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

import static org.apache.http.conn.ssl.SSLConnectionSocketFactory.getDefaultHostnameVerifier;

public class GCPZTSCredentials {

private final String keyFile;
private final String certFile;
private final String trustStorePath;
private final String audience;
private final String serviceUrl;
private final String tokenUrl;
private final char[] trustStorePassword;
private Proxy proxy = null;
int certRefreshTimeout;
int tokenLifetimeSeconds = 3600;
KeyRefresher keyRefresher = null;
KeyRefresher keyRefresher;
final AthenztHttpTransportFactory httpTransportFactory;
final InputStream tokenApiStream;

/**
* Internal constructor with required details. See {@link GCPZTSCredentials.Builder}.
*
* @param builder the {@code Builder} object used to construct the credentials.
*/
GCPZTSCredentials(Builder builder) {

this.keyFile = builder.keyFile;
this.certFile = builder.certFile;
this.trustStorePath = builder.trustStorePath;
this.trustStorePassword = builder.trustStorePassword;
this.certRefreshTimeout = builder.certRefreshTimeout;
if (builder.tokenLifetimeSeconds > 0) {
this.tokenLifetimeSeconds = builder.tokenLifetimeSeconds;
}
audience = String.format("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
GCPZTSCredentials(Builder builder) throws KeyRefresherException, IOException, InterruptedException {

final String audience = String.format("//iam.googleapis.com/projects/%s/locations/global/workloadIdentityPools/%s/providers/%s",
builder.projectNumber, builder.workloadPoolName, builder.workloadProviderName);
serviceUrl = String.format("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s@%s.iam.gserviceaccount.com:generateAccessToken",
final String serviceUrl = String.format("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s@%s.iam.gserviceaccount.com:generateAccessToken",
builder.serviceAccountName, builder.projectId);
final String scope = URLEncoder.encode(ZTSClient.generateIdTokenScope(builder.domainName, builder.roleNames), StandardCharsets.UTF_8);
final String redirectUri = URLEncoder.encode(ZTSClient.generateRedirectUri(builder.clientId, builder.redirectUriSuffix), StandardCharsets.UTF_8);
tokenUrl = String.format("%s/oauth2/auth?response_type=id_token&client_id=%s&redirect_uri=%s&scope=%s&nonce=%s&keyType=EC&fullArn=true&output=json",
final String tokenUrl = String.format("%s/oauth2/auth?response_type=id_token&client_id=%s&redirect_uri=%s&scope=%s&nonce=%s&keyType=EC&fullArn=true&output=json",
builder.ztsUrl, builder.clientId, redirectUri, scope, Crypto.randomSalt());

// create our input stream

tokenApiStream = createTokenAPIStream(audience, serviceUrl, tokenUrl, builder.tokenLifetimeSeconds);

// generate the key refresher object based on your provided details

keyRefresher = Utils.generateKeyRefresher(builder.trustStorePath, builder.trustStorePassword,
builder.certFile, builder.keyFile);
if (builder.certRefreshTimeout > 0) {
keyRefresher.startup(builder.certRefreshTimeout);
}

SSLContext sslContext = Utils.buildSSLContext(keyRefresher.getKeyManagerProxy(),
keyRefresher.getTrustManagerProxy());

SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2", "TLSv1.3"}, null, getDefaultHostnameVerifier());

// set up http client builder with our ssl context and proxy details if configured

HttpClientBuilder httpClientBuilder = ApacheHttpTransport.newDefaultHttpClientBuilder()
.setSSLSocketFactory(sslConnectionSocketFactory);

if (builder.proxyHost != null && !builder.proxyHost.isEmpty()) {
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(builder.proxyHost, builder.proxyPort));
HttpHost proxy = new HttpHost(builder.proxyHost, builder.proxyPort);
httpClientBuilder.setRoutePlanner(new DefaultProxyRoutePlanner(proxy));
if (builder.proxyAuth != null && !builder.proxyAuth.isEmpty()) {
httpClientBuilder.setRequestExecutor(new AthenzProxyHttpRequestExecutor(builder.proxyAuth));
}
}

// finally create our transport factory

httpTransportFactory = new AthenztHttpTransportFactory(new ApacheHttpTransport(httpClientBuilder.build()));
}

/**
Expand All @@ -90,31 +112,19 @@ public void close() {
}
}


/**
* Return ExternalAccountCredentials object based on the configured details
* that could be used in other GCP SDK APIs as credentials to access requested
* GCP resources.
*
* @return ExternalAccountCredentials credentials object
*/
public ExternalAccountCredentials getTokenAPICredentials() throws KeyRefresherException,
IOException, InterruptedException {

keyRefresher = Utils.generateKeyRefresher(trustStorePath, trustStorePassword,
certFile, keyFile);
if (certRefreshTimeout > 0) {
keyRefresher.startup(certRefreshTimeout);
}
SSLContext sslContext = Utils.buildSSLContext(keyRefresher.getKeyManagerProxy(),
keyRefresher.getTrustManagerProxy());

final AthenztHttpTransportFactory transportFactory = new AthenztHttpTransportFactory(sslContext, proxy);
final InputStream inputStream = createTokenAPIStream();
return ExternalAccountCredentials.fromStream(inputStream, transportFactory);
public ExternalAccountCredentials getTokenAPICredentials() throws IOException {
return ExternalAccountCredentials.fromStream(tokenApiStream, httpTransportFactory);
}

InputStream createTokenAPIStream() {
InputStream createTokenAPIStream(final String audience, final String serviceUrl, final String tokenUrl,
int tokenLifetimeSeconds) {

GenericJson config = new GenericJson();
config.setFactory(GsonFactory.getDefaultInstance());
Expand Down Expand Up @@ -145,19 +155,33 @@ InputStream createTokenAPIStream() {

static class AthenztHttpTransportFactory implements HttpTransportFactory {

final SSLContext sslContext;
final Proxy proxy;
final HttpTransport httpTransport;

AthenztHttpTransportFactory(SSLContext sslContext, Proxy proxy) {
this.sslContext = sslContext;
this.proxy = proxy;
AthenztHttpTransportFactory(HttpTransport httpTransport) {
this.httpTransport = httpTransport;
}

public HttpTransport create() {
return new NetHttpTransport.Builder()
.setSslSocketFactory(sslContext.getSocketFactory())
.setProxy(proxy)
.build();
return httpTransport;
}
}

static class AthenzProxyHttpRequestExecutor extends HttpRequestExecutor {

final String proxyAuth;

public AthenzProxyHttpRequestExecutor(final String proxyAuth) {
super();
this.proxyAuth = proxyAuth;
}

@Override
public HttpResponse execute(HttpRequest request, HttpClientConnection conn, HttpContext context)
throws IOException, HttpException {
if ("CONNECT".equalsIgnoreCase(request.getRequestLine().getMethod())) {
request.setHeader(HttpHeaders.PROXY_AUTHORIZATION, proxyAuth);
}
return super.execute(request, conn, context);
}
}

Expand All @@ -179,9 +203,10 @@ public static class Builder {
private String trustStorePath;
private char[] trustStorePassword;
private String proxyHost;
private int proxyPort = 443;
int certRefreshTimeout;
int tokenLifetimeSeconds;
private String proxyAuth;
private int proxyPort = 4080;
int certRefreshTimeout = 0;
int tokenLifetimeSeconds = 3600;

public Builder() {
}
Expand Down Expand Up @@ -378,6 +403,7 @@ public Builder setCertRefreshTimeout(int certRefreshTimeout) {
/**
* Sets the requested lifetime for the Google access tokens.
* GCP requires that the value must be between 600 and 43200 seconds.
* Default value is 600 seconds.
*
* @param tokenLifetimeSeconds token lifetime in seconds
* @return this {@code Builder} object
Expand All @@ -404,9 +430,8 @@ public Builder setProxyHost(String proxyHost) {

/**
* Sets the proxy port number for the request. The default
* value for the proxy port is 443. A valid port value is between
* 0 and 65535. A port number of zero will let the system
* pick up an ephemeral port in a bind operation.
* value for the proxy port is 4080. A valid port value is between
* 0 and 65535.
*
* @param proxyPort proxy server port number
* @return this {@code Builder} object
Expand All @@ -419,14 +444,27 @@ public Builder setProxyPort(int proxyPort) {
return this;
}

/**
* Sets the proxy authorization value for the request. This will
* be included as the value for the Proxy-Authorization header
* if proxy host is configured.
*
* @param proxyAuth proxy-authorization header value
* @return this {@code Builder} object
*/
public Builder setProxyAuth(String proxyAuth) {
this.proxyAuth = proxyAuth;
return this;
}

/**
* Return GCPZTSCredentials object based on the builder that could be
* used to obtain ExternalAccountCredentials credentials object by using
* the getTokenAPICredentials() method of the object.
*
* @return GCPZTSCredentials object
*/
public GCPZTSCredentials build() {
public GCPZTSCredentials build() throws KeyRefresherException, IOException, InterruptedException {
return new GCPZTSCredentials(this);
}
}
Expand Down
Loading

0 comments on commit 93fdd3d

Please sign in to comment.