Is it possible to implement OIDC in front of Nginx Stream with OpenResty? - nginx-reverse-proxy

I would like to know if it is possible to use the OpenResty OIDC module as an authentication proxy within an NGINX stream configuration.
(I don't have acccess to NGINX Plus unfortunately)
I have used NGINX with Stream configurations in the past to proxy access to upstream tcp resources and it works like a charm.
I am currently looking at implementing an OIDC proxy in front of various resources, both static html and dynamic apps, because we have an in-house OIDC IDAM provider. I came across OpenResty, and in particular the lua-resty-oidc module, and thanks to some wonderful guides, (https://medium.com/#technospace/nginx-as-an-openid-connect-rp-with-wso2-identity-server-part-1-b9a63f9bef0a , https://developers.redhat.com/blog/2018/10/08/configuring-nginx-keycloak-oauth-oidc/ ), I got this working in no time for static pages, using an http server nginx config.
I can't get it working for stream configurations though. It looks like the stream module is enabled as standard for OpenResty, but from digging around I don't think the 'access_by_lua_block' function is allowed in the stream context.
This may simply not be supported, which is fair enough when begging off other people's great work, but I wondered if there was any intention to include suport within OpenResty / lua-resty-oidc in the future, or whether anyone knew of a good workaround.
This was my naive attempt to get it working but the server complains about the
'access_by_lua_block' command at run time.
2019/08/22 08:20:44 [emerg] 1#1: "access_by_lua_block" directive is not allowed here in /usr/local/openresty/nginx/conf/nginx.conf:49
nginx: [emerg] "access_by_lua_block" directive is not allowed here in /usr/local/openresty/nginx/conf/nginx.conf:49
events {
worker_connections 1024;
}
stream {
lua_package_path "/usr/local/openresty/?.lua;;";
resolver 168.63.129.16;
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
lua_ssl_verify_depth 5;
# cache for discovery metadata documents
lua_shared_dict discovery 1m;
# cache for JWKs
lua_shared_dict jwks 1m;
upstream geyser {
server geyser-api.com:3838;
}
server {
listen 443 ssl;
ssl_certificate /usr/local/openresty/nginx/ssl/nginx.crt;
ssl_certificate_key /usr/local/openresty/nginx/ssl/nginx.key;
access_by_lua_block {
local opts = {
redirect_uri_path = "/redirect_uri",
discovery = "https://oidc.provider/discovery",
client_id = "XXXXXXXXXXX",
client_secret = "XXXXXXXXXXX",
ssl_verify = "no",
scope = "openid",
redirect_uri_scheme = "https",
}
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 500
ngx.say(err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
ngx.req.set_header("X-USER", res.id_token.sub)
}
proxy_pass geyser;
}
}
Anyone have any advice?

i don't think that's possible.
However to be sure, you should try creating an issue on the official github
https://github.com/zmartzone/lua-resty-openidc/issues
They helped me solve a similar issue before

Related

Let's Encrypt SSL ( sailsjs framework )

Is there any node modules for sailsjs framework to make ssl certificate using let's encrypt?
There is a middleware that enables http->https redirect and also handles the ACME-validation requests from Let's Encrypt. As far as I can tell, it does not actually trigger the renewal, nor writes anything, but I believe that the ACME-scripts handle that as cron-jobs every 3 months or so, allowing you app to just validate automatically when they run. I haven't implemented this myself yet though.
I would also ask you to really consider using CloudFlare or some other SSL-termination service, as that also gives you a lot of other benefits like DDoS protection, some CDN-features etc.
Docs:#sailshq/lifejacket
As has been mentioned, you should consider the best overall solution in terms of CloudFlare or SSL-offload via nginx etc.
However, you can use greenlock-express.js for this to achieve SSL with LetsEncrypt directly within the Sails node environment.
The example below:
Configures an HTTP express app using greenlock on port 80 that handles the
redirects to HTTPS and the LetsEncrypt business logic.
Uses the greenlock SSL configuration to configure the primary Sails app as HTTPS on port 443.
Sample configuration for config/local.js:
// returns an instance of greenlock.js with additional helper methods
var glx = require('greenlock-express').create({
server: 'https://acme-v02.api.letsencrypt.org/directory'
, version: 'draft-11' // Let's Encrypt v2 (ACME v2)
, telemetry: true
, servername: 'domainname.com'
, configDir: '/tmp/acme/'
, email: 'myemail#somewhere.com'
, agreeTos: true
, communityMember: true
, approveDomains: [ 'domainname.com', 'www.domainname.com' ]
, debug: true
});
// handles acme-challenge and redirects to https
require('http').createServer(glx.middleware(require('redirect-https')())).listen(80, function () {
console.log("Listening for ACME http-01 challenges on", this.address());
});
module.exports = {
port: 443,
ssl: true,
http: {
serverOptions: glx.httpsOptions,
},
};
Refer to the greenlock documentation for fine-tuning configuration detail, but the above gets an out-of-the-box LetsEncrypt working with Sails.
Note also, that you may wish to place this configuration in somewhere like config/env/production.js as appropriate.

SSL redirect changes client IP address read from HTTPResponse

I am using Perfect Framework for my server side application running on an AWS EC2 instance. I am using the following code to get client IP address.
open static func someapi(request: HTTPRequest, _ response: HTTPResponse) {
var clientIP = request.remoteAddress.host }
This was working fine until I installed ssl certificate on my EC2 instance and start redirecting incoming traffic to port 443.
Now this code gives me the ip of my server, i think due to the redirect, Perfect somehow think request comes from itself.
Is there any other method to get client IP address? Or do i have to try something else?
Thanks!
For anyone struggling for the same problem, original client ip can be found in one of the header fields called "xForwardedFor" if there is a redirect, like the following:
var clientIP = request.remoteAddress.host
let forwardInfoResut = request.headers.filter { (item) -> Bool in
item.0 == HTTPRequestHeader.Name.xForwardedFor
}
if let forwardInfo = forwardInfoResut.first {
clientIP = forwardInfo.1
}
Hope this helps somebody, cheers!
Perhaps you should ask the people you are paying for support and whom manage the infrastructure how it works before asking us?
The convention, where an http connection is terminated elsewhere than the server is to inject an x-forwarded-for header. If there is already such a header, the intermediate server injects the client IP address at the front of the list.

Meteor/Apollo: SSL Access for HTTPS and WS?

I’m trying to get SSL working in Meteor for https and websockets. I’m getting this error:
(STDERR) Error: listen EACCES 0.0.0.0:443
Here's my setup.
For SSL access I have installed nourharidy/meteor-ssl. I can use any comparable package or approach that people have found useful!
As required by nourharidy/meteor-ssl, in server/main.js I have:
SSL('/path/to/private/server.key','/path/to/private/server.crt', 443);
Here's the rest of my setup:
My ROOT_URL environment variable is:
https://10.0.1.10:443 //I’m using my Mac’s ip address so that another Mac can access it
In imports/startup/server/index.js I have:
//SET UP APOLLO QUERY / MUTATIONS / PUBSUB
const USING_HTTPS = true;
const httpProtocol =  USING_HTTPS ? "https" : "http";
const localHostString = '10.0.1.10' //I’m using my Mac’s ip address so that another Mac can access it
const METEOR_PORT = 443;
const GRAPHQL_SUBSCRIPTION_PORT = 4000;
const subscriptionsEndpoint = `wss://${localHostString}:${GRAPHQL_SUBSCRIPTION_PORT}/subscriptions`;
const server = express();
server.use('*', cors({ origin: `${httpProtocol}://${localHostString}:${GRAPHQL_SUBSCRIPTION_PORT}` }));
server.use('/graphql', bodyParser.json(), graphqlExpress({
    schema
}));
server.use('/graphiql', graphiqlExpress({
    endpointURL: '/graphql',
    subscriptionsEndpoint: subscriptionsEndpoint
}));
// We wrap the express server so that we can attach the WebSocket for subscriptions
const ws = createServer(server);
ws.listen(GRAPHQL_SUBSCRIPTION_PORT, () => {
    console.log(`GraphQL Server is now running on ${httpProtocol}://${localHostString}:${GRAPHQL_SUBSCRIPTION_PORT}`);
    // Set up the WebSocket for handling GraphQL subscriptions
    new SubscriptionServer({
        execute,
        subscribe,
        schema
    }, {
        server: ws,
        path: '/subscriptions',
    });
});
What am I missing?
Thanks very much in advance to all for any info!
The way Stack Overflow works is that you put some effort in, and get close to the problem, and we'll help you get through that last bit. Asking for help on how to use Google is what we call off-topic.
If you have done a Google search for Error: listen EACCES 0.0.0.0:443 would have yielded a bunch of results, the first of which is this:
Node.js EACCES error when listening on most ports
So I am willing to be helpful, but you need to help yourself too
Solved it! It turns out my server setup was fine.
Server-side I was building the WSS url like so:
var websocketUri = Meteor.absoluteUrl('subscriptions').replace(/http/, 'ws').replace('3100', '3200');
The Meteor docs for absoluteUrl say:
The server reads from the ROOT_URL environment variable to determine where it is running.
In Webstorm I have ROOT_URL set to https://10.0.nn.nn:3100. But for some reason absoluteUrl was returning http://10.0.nn.nn:3000. I.e. it had the wrong port.
After correcting the port-- it’s working!
UPDATE: I thought I had it solved when I posted a few days ago, but I was still missing something.
I'm now using ngrok to provide SSL to my dev system for use with HTTPS and WSS.
Here are a few details.
I run Meteor on localhost:3000.
I assign the value of "http://localhost:3000" to the ROOT_URL environment variable.
I run two simultaneous ngrok connections, one for HTTPS and one for WSS:
./ngrok http 3000
./ngrok http 3200
This gives me two ngrok urls, for example:
Forwarding https://9b785bd3.ngrok.io -> localhost:3000
Forwarding https://ec3d8027.ngrok.io -> localhost:3200
I access my app via the ngrok url, for example:
https://9b785bd3.ngrok.io
I build my HTTPS links and WSS links based on the ngrok addresses. For example:
const wssEndpoint = 'wss://ec3d8027.ngrok.io/subscriptions';
I hope this is helpful to others looking into this.

Does Rikulo Stream support server-side SSL?

In my search to find SSL support, I have looked at the Rikulo Security package, which unfortunately does not support SSL.
If it does not support SSL, it would be nice if the url mapping could define this somehow (similar to how security plugin does it in Grails), and with config parameter for the path of the SSL certificate.
An example of the way it could be configured:
var urlMap = {
"/": home,
"/login": SECURE_CHANNEL(login), // I made this part up
.....
};
new StreamServer(uriMapping: urlMap)
..start(port: 8080);
Has anyone got SSL working with Rikulo Stream?
First, you shall use startSecure():
new StreamServer()
..start(port: 80)
..startSecure(address: "11.22.33.44", port: 443);
Second, the routing map shall be the same, i.e., no special handling.
If you'd like to have different routing map for HTTP and HTTPS, you can start two servers:
new StreamServer(mapping1).start(port: 80);
new StreamServer(mapping2).startSecure(address: "11.22.33.44", port: 443);

How to use TLS in Play!Framework WebSockets ("wss://")

I cannot use wss:// in my simple WebSocket app created with Play!Framework 2.2. It echoes the message back. The endpoint is like this
def indexWS2 = WebSocket.using[String] {
request => {
println("got connection to indexWS2")
var channel: Option[Concurrent.Channel[String]] = None
val outEnumerator: Enumerator[String] = Concurrent.unicast(c => channel = Some(c))
// Log events to the console
val myIteratee: Iteratee[String, Unit] = Iteratee.foreach[String] {gotString => {
println("received: " + gotString)
// send string back
channel.foreach(_.push("echoing back \"" + gotString + "\""))
}}
(myIteratee, outEnumerator)
}
}
and the route is described as
GET /ws2 controllers.Application.indexWS2
I create a connection from a JS client like this
myWebSocket = new WebSocket("ws://localhost:9000/ws2");
and everything works fine. But if I change ws:// into wss:// in order to use TLS, it fails and I get the following Netty exception:
[error] p.nettyException - Exception caught in Netty
java.lang.IllegalArgumentException: empty text
How can I make this work? Thanks.
I really wanted to figure this out for you! But I didn't like the answer. It appears there's no Play support yet for SSL for websockets. Saw mention of it here and no sign of progress since:
http://grokbase.com/t/gg/play-framework/12cd53wst9/2-1-https-and-wss-secure-websocket-clarifications-and-documentation
However, there's hope! You can use nginx as a secure websocket (wss) endpoint, to forward to a internal play app with a insecure websocket endpoint:
The page http://siriux.net/2013/06/nginx-and-websockets/ provided this explanation and sample proxy config for nginx:
Goal: WSS SSL Endpoint: forwards wss|https://ws.example.com to ws|http://ws1.example.com:10080
"The proxy is also an SSL endpoint for WSS and HTTPS connections. So the clients can use wss:// connections (e.g. from pages served via HTTPS) which work better with broken proxy servers, etc."
server {
listen 443;
server_name ws.example.com;
ssl on;
ssl_certificate ws.example.com.bundle.crt;
ssl_certificate_key ws.example.com.key;
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
# like above
}
}
Nginx is so lightweight and fun. Would not hesitate to go with this option.
Did you try enabling https support on the Play server? It looks like you're trying to connect to the http port using wss, that can never work, you need to enable https, and then change the URL not just to wss, but also to use the https port.
To start a Play server with ssl turned on:
activator run -Dhttps.port=9443
Then connect to wss://localhost:9443/ws2.
wss works fine with Play 2.6.
Instead of hardcode the websocket url, you can get the url via routes:
#import play.api.mvc.RequestHeader
#import controllers.routes
#()(implicit request: RequestHeader)
<!DOCTYPE html>
<html lang="en">
<head>
<title>...</title>
<script>
var wsUri = "#routes.MyController.indexWS2().webSocketURL(secure = true)";
var webSocket = new WebSocket(wsUri);
//...
</script>
</head>
<body>
...
</body>
</html>
Another option is to use SockJS as the Websocket layer, SockJS implementation for Play2 can be found at
https://github.com/fdimuccio/play2-sockjs
When HTTPS is enabled an wss endpoind is created by SockJS over the HTTPS channel. Play2-sockjs also supports the Actor pattern as with native Play websockets.
If you don't want to use SockJS in the clientside but rather force browser websocket implementation, you can use explicit websocket endpoint wss:////websocket