Google Cloud Pubsub: HTTP version error when publishing message via proxy with PublisherClient - proxy

We publish messages to PubSub using the PublisherClient. When running from the production environment we set a proxy. However, when the code executes it throws the following error:
Only HTTP/1.0 and HTTP/1.1 version requests are currently supported. Parameter name: value
How do I set for example http 1.1 when I create the PublisherClient?
The line that throws the exception is:
return await publisherClient.PublishAsync(message);
The code currently looks like this:
public async Task<string> PublishMessage(string projectId, string topicId, string message)
{
TopicName topicName = TopicName.FromProjectTopic(projectId, topicId);
PublisherClient publisherClient = await GetPublisherClient(topicName);
try
{
return await publisherClient.PublishAsync(message);
}
catch (Exception r)
{
throw;
}
}
private PublisherClient publisherClient1 = null;
private async Task<PublisherClient> GetPublisherClient(TopicName topicName)
{
if (publisherClient1 == null)
{
publisherClient1 = _useProxy ? GetClientWithProxy(topicName) : await PublisherClient.CreateAsync(topicName);
}
return publisherClient1;
}
private PublisherClient GetClientWithProxy(TopicName topicName)
{
WebProxy proxy = new WebProxy(_nordnetProxyUrl, _nordnetWebProxyPort);
PublisherClient publisher = new PublisherClientBuilder
{
TopicName = topicName,
GrpcAdapter = GrpcNetClientAdapter.Default.WithAdditionalOptions(options => options.HttpHandler = new HttpClientHandler
{
Proxy = proxy,
UseProxy = true
})
}.Build();
return publisher;
}

Related

Can API Key and JWT Token be used in the same .Net 6 WebAPI

I am building a new .Net 6 WebAPI that will be consumed by many applications so I need to implement API Keys to limit access to only those applications. Only a very small amount of the individual users will require authorization (admins) so I would like to combine with JWT for the Admin endpoints. We do not want to require users to have to crate an account where not necessary (non-admins). Is this possible? Thank You.
Yes it is possible.
The solution I recommend is to setup multiple authentication methods in asp.net core 6 using two authentication schemes that you have to specify inside Authorize attribute.
Here a simple implementation of ApiKey authentication:
namespace MyAuthentication;
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private enum AuthenticationFailureReason
{
NONE = 0,
API_KEY_HEADER_NOT_PROVIDED,
API_KEY_HEADER_VALUE_NULL,
API_KEY_INVALID
}
private readonly Microsoft.Extensions.Logging.ILogger _logger;
private AuthenticationFailureReason _failureReason = AuthenticationFailureReason.NONE;
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory loggerFactory,
ILogger<ApiKeyAuthenticationHandler> logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, loggerFactory, encoder, clock)
{
_logger = logger;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//ApiKey header get
if (!TryGetApiKeyHeader(out string providedApiKey, out AuthenticateResult authenticateResult))
{
return authenticateResult;
}
//TODO: you apikey validity check
if (await ApiKeyCheckAsync(providedApiKey))
{
var principal = new ClaimsPrincipal(); //TODO: Create your Identity retreiving claims
var ticket = new AuthenticationTicket(principal, ApiKeyAuthenticationOptions.Scheme);
return AuthenticateResult.Success(ticket);
}
_failureReason = AuthenticationFailureReason.API_KEY_INVALID;
return AuthenticateResult.NoResult();
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
//Create response
Response.Headers.Append(HeaderNames.WWWAuthenticate, $#"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}""");
Response.StatusCode = StatusCodes.Status401Unauthorized;
Response.ContentType = MediaTypeNames.Application.Json;
//TODO: setup a response to provide additional information if you want
var result = new
{
StatusCode = Response.StatusCode,
Message = _failureReason switch
{
AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED => "ApiKey not provided",
AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL => "ApiKey value is null",
AuthenticationFailureReason.NONE or AuthenticationFailureReason.API_KEY_INVALID or _ => "ApiKey is not valid"
}
};
using var responseStream = new MemoryStream();
await JsonSerializer.SerializeAsync(responseStream, result);
await Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
{
//Create response
Response.Headers.Append(HeaderNames.WWWAuthenticate, $#"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}""");
Response.StatusCode = StatusCodes.Status403Forbidden;
Response.ContentType = MediaTypeNames.Application.Json;
var result = new
{
StatusCode = Response.StatusCode,
Message = "Forbidden"
};
using var responseStream = new MemoryStream();
await JsonSerializer.SerializeAsync(responseStream, result);
await Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
#region Privates
private bool TryGetApiKeyHeader(out string apiKeyHeaderValue, out AuthenticateResult result)
{
apiKeyHeaderValue = null;
if (!Request.Headers.TryGetValue("X-Api-Key", out var apiKeyHeaderValues))
{
_logger.LogError("ApiKey header not provided");
_failureReason = AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED;
result = AuthenticateResult.Fail("ApiKey header not provided");
return false;
}
apiKeyHeaderValue = apiKeyHeaderValues.FirstOrDefault();
if (apiKeyHeaderValues.Count == 0 || string.IsNullOrWhiteSpace(apiKeyHeaderValue))
{
_logger.LogError("ApiKey header value null");
_failureReason = AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL;
result = AuthenticateResult.Fail("ApiKey header value null");
return false;
}
result = null;
return true;
}
private Task<bool> ApiKeyCheckAsync(string apiKey)
{
//TODO: setup your validation code...
return Task.FromResult<bool>(true);
}
#endregion
}
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "ApiKey";
public static string Scheme => DefaultScheme;
public static string AuthenticationType => DefaultScheme;
}
public static class AuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddApiKeySupport(this AuthenticationBuilder authenticationBuilder, Action<ApiKeyAuthenticationOptions> options)
=> authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationOptions.DefaultScheme, options);
}
Then register inside builder setup:
_ = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme;
options.DefaultChallengeScheme = ApiKeyAuthenticationOptions.DefaultScheme;
})
.AddApiKeySupport(options => { });
You have to also setup the standard JWT Bearer validation (I don't post it for the sake of brevity).
To protect your endpoint add the Authorize attribute like:
[Authorize(AuthenticationSchemes = ApiKeyAuthenticationOptions.DefaultScheme)] //ApiKey
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
//or..
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] //Jwt
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
//or..
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme},{ApiKeyAuthenticationOptions.DefaultScheme}" )] //ApiKey and Jwt
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
For me it is the best way so as to carry out the authorization check before the start of the application pipeline (fail fast) and to be able to create the user identity.
But if you don't need to put informations about the Api Key inside the ClaimsPrincipal and only check the validity of Api Key the simplest way to do that is:
Protect the "admin" actions with JWT auth (with Authorize attribute)
Setup and register a middleware to only check the Api Key in all actions
Here is an example:
public class SimpleApiKeyMiddleware
{
private static readonly string API_KEY_HEADER = "X-Api-Key";
private readonly RequestDelegate _next;
private readonly ILogger<SimpleApiKeyMiddleware> _logger;
public SimpleApiKeyMiddleware(RequestDelegate next, ILogger<SimpleApiKeyMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext httpContext)
{
//Get apikey header
if (!httpContext.Request.Headers.TryGetValue(API_KEY_HEADER, out var apiKey))
{
_logger.LogError("ApiKey not found inside request headers");
//Error and exit from asp.net core pipeline
await GenerateForbiddenResponse(httpContext, "ApiKey not found inside request headers");
}
else if (!await ApiKeyCheckAsync(apiKey))
{
_logger.LogError("ApiKey is not valid: {ApiKey}", apiKey);
//Error and exit from asp.net core pipeline
await GenerateForbiddenResponse(httpContext, "ApiKey not valid");
}
else
{
_logger.LogInformation("ApiKey validated: {ApiKey}", apiKey);
//Proceed with pipeline
await _next(httpContext);
}
}
private Task<bool> ApiKeyCheckAsync(string apiKey)
{
//TODO: setup your validation code...
return Task.FromResult<bool>(true);
}
private async Task GenerateForbiddenResponse(HttpContext context, string message)
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
context.Response.ContentType = MediaTypeNames.Application.Json;
using var responseStream = new MemoryStream();
await System.Text.Json.JsonSerializer.SerializeAsync(responseStream, new
{
Status = StatusCodes.Status403Forbidden,
Message = message
});
await context.Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
}
Registration:
_ = app.UseMiddleware<ApiKeyMiddleware>(); //Register as first middleware to avoid other middleware execution before api key check
Usage:
//Admin: Jwt and Api Key check
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] //Jwt and Api Key
[HttpGet]
public async Task<IActionResult> MyAdminApi()
{
//...omissis...
}
//Non Admin: Api Key check only
[HttpGet]
public async Task<IActionResult> MyNonAdminApi()
{
//...omissis...
}
Note: the middleware code above forces exit from pipeline returning an http result so as to stop next middleware execution. Also note that the asp.net core 6 pipeline executes Authorization first and then all the registered middlewares.

Can't catch HttpRequestException when using HttpMessageHandler

I'm using HttpMessageHandler to log Http Request and Response. If internet connection is not available I get HttpRequestException, but this Exception is not catched (try catch not working). If I don't use HttpMessageHandler Exception is catched.
Method with try catch :
public JObject Get()
{
string url = "/...";
try
{
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert,
chain, errors) =>
{ return true; };
using (var client = new HttpClient(new LoggingHandler(httpClientHandler)))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue(CONTENT_TYPE_JSON));
var byteArray = Encoding.ASCII.GetBytes("");
client.DefaultRequestHeaders.Authorization = new
System.Net.Http.Headers.AuthenticationHeaderValue("Basic",
Convert.ToBase64String(byteArray));
HttpResponseMessage response = client.GetAsync(url).Result;
response.EnsureSuccessStatusCode();
var json = response.Content.ReadAsStringAsync().Result;
JObject rss = JObject.Parse(json);
return rss;
}
}
}
catch(HttpRequestException ex)
{
//Catch not working
return null;
}
}
HttpMessageHandler :
public class LoggingHandler : DelegatingHandler
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(LoggingHandler));
public LoggingHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
Loging("Request:");
Loging(request.ToString());
if (request.Content != null)
{
Loging(await request.Content.ReadAsStringAsync());
}
Loging("");
response = await base.SendAsync(request, cancellationToken);
Loging("Response:");
Loging(response.ToString());
if (response.Content != null)
{
Loging(await response.Content.ReadAsStringAsync());
}
Loging("");
return response;
}
private void Loging(string message)
{
Debug.WriteLine(message);
log.Info(message);
}
}
Why is it happining?

Vertx http post client runs forever

I have the following Vertx Route setup:
router.post("/api/apple/")
.handler(e -> {
e.response()
.putHeader("content-type", "application/json")
.setStatusCode(200)
.end("hello");
})
.failureHandler(ctx -> {
LOG.error("Error: "+ ctx.response().getStatusMessage());
ctx.response().end();
});
vertx.createHttpServer().requestHandler(router::accept)
.listen(config().getInteger("http.port", 8081), result -> {
if (result.succeeded()) {
LOG.info("result succeeded in my start method");
future.complete();
} else {
LOG.error("result failed");
future.fail(result.cause());
}
});
When I call this from my Java test client:
Async async = context.async();
io.vertx.core.http.HttpClient client = vertx.createHttpClient();
HttpClientRequest request = client.post(8081, "localhost", "/api/apple/", response -> {
async.complete();
LOG.info("Some callback {}",response.statusCode());
});
String body = "{'username':'www','password':'www'}";
request.putHeader("content-length", "1000");
request.putHeader("content-type", "application/x-www-form-urlencoded");
request.write(body);
request.end();
The client keeps running and then the client times out. Seems like it is not able to find the endpoint on localhost:8081/api/apple
You didn't deploy your verticle defining routes in the test scope. Here is a working snippet:
public class HttpServerVerticleTest extends VertxTestRunner {
private WebClient webClient;
private HttpServerVerticle httpServer;
private int port;
#Before
public void setUp(TestContext context) throws IOException {
port = 8081;
httpServer = new HttpServerVerticle(); // the verticle where your routes are registered
// NOTICE HERE
vertx.deployVerticle(httpServer, yourdeploymentOptions, context.asyncAssertSuccess());
webClient = WebClient.wrap(vertx.createHttpClient());
}
#After
public void tearDown(TestContext testContext) {
webClient.close();
vertx.close(testContext.asyncAssertSuccess());
}
#Test
public void test_my_post_method(TestContext testContext) {
Async http = testContext.async();
String body = "{'username':'www','password':'www'}";
webClient.post(port, "localhost", "/api/apple/")
//.putHeader("Authorization", JWT_TOKEN)
.putHeader("content-length", "1000");
.putHeader("content-type", "application/x-www-form-urlencoded");
.sendJson(Buffer.buffer(body.getBytes()), requestResponse -> {
if (requestResponse.succeeded()) {
testContext.assertTrue(requestResponse.result().statusCode() == HttpResponseStatus.OK.code());
testContext.assertTrue(requestResponse.result().body().getString().equals("hello"));
} else {
testContext.fail(requestResponse.cause());
}
http.complete();
});
}
}

Http proxy in Netty websocket client to connect to internet

My application is running behind a corporate firewall and I need to use http proxy(http://theclientproxy.net:8080) to connect to internet
I have used the Netty client as below,
https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http/websocketx/client
Code:
public final class WebSocketClient {
static final String URL = System.getProperty("url", "wss://127.0.0.1:8080/websocket");
public static void main(String[] args) throws Exception {
URI uri = new URI(URL);
String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
final int port;
final boolean ssl = "wss".equalsIgnoreCase(scheme);
final SslContext sslCtx;
if (ssl) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
final WebSocketClientHandler handler =
new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addFirst(new HttpProxyHandler(new InetSocketAddress("theclientproxy.net", 8080) ) );
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
}
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
WebSocketClientCompressionHandler.INSTANCE,
handler);
}
});
Channel ch = b.connect(uri.getHost(), port).sync().channel();
handler.handshakeFuture().sync();
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String msg = console.readLine(); //THIS IS NULL IN DATA CENTER LOGS
if (msg == null) {
break;
} else if ("bye".equals(msg.toLowerCase())) {
ch.writeAndFlush(new CloseWebSocketFrame());
ch.closeFuture().sync();
break;
} else if ("ping".equals(msg.toLowerCase())) {
WebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] { 8, 1, 8, 1 }));
ch.writeAndFlush(frame);
} else {
WebSocketFrame frame = new TextWebSocketFrame(msg);
ch.writeAndFlush(frame);
}
}
} finally {
group.shutdownGracefully();
}
Handler:
public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
private final WebSocketClientHandshaker handshaker;
private ChannelPromise handshakeFuture;
public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
this.handshaker = handshaker;
}
public ChannelFuture handshakeFuture() {
return handshakeFuture;
}
#Override
public void handlerAdded(ChannelHandlerContext ctx) {
handshakeFuture = ctx.newPromise();
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
handshaker.handshake(ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("WebSocket Client disconnected!");
}
#Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel ch = ctx.channel();
if (!handshaker.isHandshakeComplete()) {
try {
handshaker.finishHandshake(ch, (FullHttpResponse) msg);
System.out.println("WebSocket Client connected!");
handshakeFuture.setSuccess();
} catch (WebSocketHandshakeException e) {
System.out.println("WebSocket Client failed to connect");
handshakeFuture.setFailure(e);
}
return;
}
The application is able to connect to the websocket server endpoint from my local machine successfully.
But in the company datacenter where my application is deployed, I see the msg value is null and the websocket client is disconnected
Does that mean my connection is blocked at firewall? If that is the case then why did the statement "WebSocket Client connected!" is printed at all?
Thanks
The httpproxyhandler you used is correct
Just remove the BufferredReader code as mentioned below when deploying in linux, docker, etc:
Netty WebSocket Client Channel always gets inactive on Linux Server

Web service request working in old version of android (2.3.3) but not in later versions (4.0.3, 4.3)

While working on an application for android that uses web services I encounterd a bad request (response code 400) message when trying to retrieve some data in android versions 4.0.3 and 4.3. The perculiar thing however is that when sending the same request using the same code but on a device using android version 2.3.3 it works without any problems. I have also tried using httpGet instead of HttpsURLConnection, while this work for all versions it does not provide a solution as I need the added security.
My code is as follows:
private String executeRequest(String urlAddress)
{
String responce = null;
String msg = null;
int error = 0;
try {
URL url = new URL(urlAddress);
HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
SSLSocketFactory factory = SecureSocketFactory.getSSLSocketFactory();
connection.setSSLSocketFactory(factory);
connection.setHostnameVerifier(new Verifier());
connection.setDoOutput(true);
connection.setDoInput(true);
if (method == RequestMethod.POST)
{
connection.setRequestMethod("POST");
}
msg = connection.getResponseMessage();
error = connection.getResponseCode();
if ("OK".equals(msg))
{
InputStream content = (InputStream) connection.getContent();
responce = convertStreamToString(content);
}
else
{
responce = "Error " + error;
}
connection.disconnect();
} catch (Exception e) {
responce = e.toString();
}
return responce;
}
And the code of SecureSocketFactory.getSSLSocketFactory():
public static SSLSocketFactory getSSLSocketFactory()
throws IOException
{
if(ssf_ == null)
{
javax.net.ssl.KeyManager kms[] = null;
javax.net.ssl.TrustManager tms[] = null;
SSLContext context = null;
try
{
tms = CustomTrustManager.getTrustManagers();
context = SSLContext.getInstance("TLS");
context.init(kms, tms, null);
}
catch(GeneralSecurityException e)
{
IOException io = new IOException(e.getLocalizedMessage());
io.setStackTrace(e.getStackTrace());
throw io;
}
ssf_ = context.getSocketFactory();
}
return ssf_;
}
and the code of CustomTrustManager.getTrustManagers()
static TrustManager[] getTrustManagers(String trustStoreFile, String trustStorePW)
throws NoSuchAlgorithmException, KeyStoreException
{
String alg = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmFact = TrustManagerFactory.getInstance(alg);
tmFact.init((KeyStore)null);
TrustManager tms[] = tmFact.getTrustManagers();
for(int i = 0; i < tms.length; i++)
if(tms[i] instanceof X509TrustManager)
tms[i] = new CustomTrustManager((X509TrustManager)tms[i]);
return tms;
}
static TrustManager[] getTrustManagers()
throws NoSuchAlgorithmException, KeyStoreException
{
return getTrustManagers(null, null);
}
I have looked everywhere, but can't seem to find a solution please help.
I found my error, because do connection.setDoInput(true) it silencly sets my Requestmethod to post in version 4 which gives an error on the server causing it to return bad request.
apparently it does not set this in version 2, which explains why it does work there.
The following execute request method change fixed my code:
private String executeRequest(String urlAddress)
{
String responce = null;
String msg = null;
int error = 0;
try {
URL url = new URL(urlAddress);
HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
SSLSocketFactory factory = SecureSocketFactory.getSSLSocketFactory();
connection.setSSLSocketFactory(factory);
connection.setHostnameVerifier(new Verifier());
if (method == RequestMethod.POST)
{
connection.setDoOutput(true);
connection.setRequestMethod("POST");
}
else
{
connection.setDoInput(true);
connection.setRequestMethod("GET");
}
msg = connection.getResponseMessage();
error = connection.getResponseCode();
if ("OK".equals(msg))
{
InputStream content = (InputStream) connection.getContent();
responce = convertStreamToString(content);
}
else
{
responce = "Error " + error;
}
connection.disconnect();
} catch (Exception e) {
responce = e.toString();
}
return responce;
}

Resources