Faye with nodejs over HTTPS - ruby-on-rails

I'm trying to setup my production server to use faye messages using nodejs and HTTPS, but no luck.
What I have until now is:
A faye + nodejs server setup file:
var https = require('https');
var faye = require('faye');
var fs = require('fs');
var options = {
key: fs.readFileSync('/etc/httpd/ssl/example.com.key'),
cert: fs.readFileSync('/etc/httpd/ssl/example.com.crt'),
ca: fs.readFileSync('/etc/httpd/ssl/ca_bundle.crt')
};
var server = https.createServer(options);
var bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 60});
bayeux.attach(server);
server.listen(8000);
A rails helper to send messages:
def broadcast(channel, &block)
message = {:channel => channel, :data => capture(&block)}
uri = URI.parse(Rails.configuration.faye_url)
Net::HTTPS.post(uri, message.to_json)
end
A javascript function to open a listener:
function openListener(channel, callback){
var faye_client = new Faye.Client("<%= Rails.configuration.faye_url %>");
faye_client.subscribe(channel , callback);
return faye_client;
}
My faye url config in production.rb:
config.faye_url = "https://example.com:8000/faye"
And finally, a call in my page javascript:
fayeClient = openListener("my_channel" , function(data) {
//do something...
});
Everything was working when testing over http on development machine. But in production don't.
If I point browser to https://example.com:8000/faye.js I got the correct javascript file.
What could be happen?

The problem was with Apache server.
I had switch to nginx and now it´s working.
However, I need to make some configurations:
Faye + node.js setup file:
var http = require('http'),
faye = require('faye');
var server = http.createServer(),
bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 60});
bayeux.attach(server);
server.listen(8000);
Rails helper:
def broadcast(channel, &block)
message = {:channel => channel, :data => capture(&block)}
uri = URI.parse(Rails.configuration.faye_url)
Net::HTTP.post_form(uri, :message => message.to_json)
end
Faye url:
https://example.com/faye
And finally, nginx config
server {
# Listen on 80 and 443
listen 80;
listen 443 ssl;
server_name example.com;
passenger_enabled on;
root /home/rails/myapp/public;
ssl_certificate /home/rails/ssl/myapp.crt;
ssl_certificate_key /home/rails/ssl/myapp.key;
# Redirect all non-SSL traffic to SSL.
if ($ssl_protocol = "") {
rewrite ^ https://$host$request_uri? permanent;
}
location /faye {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Short words: nginx convert https requests in /faye address, to http in port 8000.
Use default http in server side, and https in client side.

Related

How to Reverse Proxy using Nginx to Dockerized Swagger UI's along with web apis?

I have 2 servers, one with dockerized nginx and one with 3 dockerized web apis allowing traffic through different ports (say 441, 442, 443) which has swagger UI along with it respectively.
with limited knowledge on nginx, I am trying to reverse proxy to all the swagger UI endpoints using the nginx container. This is how my nginx conf looks like, but it doesnt work as expected, it would be great if someone can advice where I am going wrong.
I am able to hit the service with the exact match location context /FileService which return index.html. But index.html has the script call where nginx fails to serve these static contents.
index.html
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
nginx.conf
server {
listen 443 ssl http2;
server_name www.webby.com;
access_log /var/log/nginx/access.log;
ssl_certificate /etc/ssl/yyyy.crt;
ssl_certificate_key /etc/ssl/xxxx.key;
ssl_protocols TLSv1.2;
if ($http_referer = 'https://$host/FileService') {
rewrite ^/(\w+) /swagger/fileservice/$1;
}
if ($http_referer = 'https://$host/PreProcess') {
rewrite ^/(\w+) /swagger/preprocess/$1;
}
location = /FileService {
proxy_pass 'http://appy.com:441/swagger/index.html';
}
location = /PreProcess {
proxy_pass 'http://appy.com:442/swagger/index.html';
}
# curl http://appy.com:441/swagger/swagger-ui-bundle.js is giving the js on this container
location ~* /swagger/fileservice(.*) {
proxy_pass 'http://appy.com:441/swagger/$1';
}
location ~* /swagger/preprocess(.*) {
proxy_pass 'http://appy.com:442/swagger/$1';
}
}
accesslog on the nginx looks like
anyways I struggled my way to implement this. Not sure if its the right approach (because I read on the internet that if block inside location context is evil), but works for my case. Feel free to correct my answer
server {
listen 443 ssl http2;
server_name www.webby.com;
access_log /var/log/nginx/access.log;
ssl_certificate /etc/ssl/yyyy.crt;
ssl_certificate_key /etc/ssl/xxxx.key;
ssl_protocols TLSv1.2;
location = /FileService {
proxy_pass 'http://appy.com:441/swagger/index.html';
}
location = /PreProcess {
proxy_pass 'http://appy.com:442/swagger/index.html';
}
location ~ ^/swagger/(.*)$ {
if ($http_referer = 'https://$host/FileService') {
proxy_pass 'http://appy.com:441/swagger/$1';
}
if ($http_referer = 'https://$host/PreProcess') {
proxy_pass 'http://appy.com:442/swagger/$1';
}
}
location ~ ^/swagger(.*)$ {
if ($http_referer = 'https://$host/FileService') {
proxy_pass 'http://appy.com:441/swagger/swagger$1';
}
if ($http_referer = 'https://$host/PreProcess') {
proxy_pass 'hhttp://appy.com:442/swagger/swagger$1';
}
}
}

Configure redirect Uris of Identity server in docker environment

Okay, this quite big so just skip to the last section for a brief.
I have a demo application (netcore 6.0) built on micro-service architect, suppose we have 3 services:
identity (Auth service - IdentityServer4)
frontend (mvc - aspnet)
nginx (reverse proxy server)
and all three are running on docker environment here is the docker-compose file
services:
demo-identity:
image: ${DOCKER_REGISTRY-}demoidentity:lastest
build:
context: .
dockerfile: Identity/Demo.Identity/Dockerfile
ports:
- 5000:80 //only export port 80,
volumes:
- ./Identity/Demo.Identity/Certificate:/app/Certificate:ro
networks:
- internal
demo-frontend:
image: ${DOCKER_REGISTRY-}demofrontend:lastest
build:
context: .
dockerfile: Frontend/Demo.Frontend/Dockerfile
ports:
- 5004:80 //only export port 80,
networks:
- internal
proxy:
build:
context: ./nginx-reverse-proxy
dockerfile: Dockerfile
ports:
- 80:80
- 443:443
volumes:
- ./nginx-reverse-proxy/cert/:/etc/cert/
links:
- demo-identity
depends_on:
- demo-identity
- demo-frontend
networks:
- internal
They all design to run internal, but nginx, it will be the proxy server, and here is the nginx.config file
worker_processes 4;
events { worker_connections 1024; }
http {
upstream app_servers_identity {
server demo-identity:80;
}
upstream app_servers_frontend {
server demo-frontend:80;
}
server {
listen 80;
listen [::]:80;
server_name demo-identity;
return 301 https://identity.demo.local$request_uri;
}
server {
listen 80;
listen [::]:80;
server_name identity.demo.local;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name identity.demo.local;
ssl_certificate /etc/cert/demo.crt;
ssl_certificate_key /etc/cert/demo.key;
location / {
proxy_pass http://app_servers_identity;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
server {
listen 80;
listen [::]:80;
server_name frontend.demo.local;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name frontend.demo.local;
ssl_certificate /etc/cert/demo.crt;
ssl_certificate_key /etc/cert/demo.key;
location / {
proxy_pass http://app_servers_frontend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
}
also I update the host file to configure two virtual hosts identity.demo.local and frontend.demo.local (the term "localhost" sometimes confusing me when using docker.)
Then I setup the identity server like this
...
builder.Services.Configure<IdentityOptions>(options => {
// Default Password settings.
});
services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddAspNetIdentity<ApplicationUser>()
.AddSigningCredential(new X509Certificate2("./Certificate/demo_dev.pfx", "******"));
...
and here is the client static config
...
new Client
{
ClientName = "MVC Client",
ClientId = "mvc-client",
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = new List<string>{ "http://gateway.demo.local/signin-oidc"},
RequirePkce = false,
AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile },
ClientSecrets = { new Secret("MVCSecret".Sha512()) }
}
...
In the Frontend service, I also configure Oidc as below
...
services.AddAuthentication(opt =>
{
opt.DefaultScheme = "Cookies";
opt.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", opt => {
opt.CookieManager = new ChunkingCookieManager();
opt.Cookie.HttpOnly = true;
opt.Cookie.SameSite = SameSiteMode.None;
opt.Cookie.SecurePolicy = CookieSecurePolicy.Always;
})
.AddOpenIdConnect("oidc", opt => {
opt.SignInScheme = "Cookies";
opt.Authority = "http://demo-identity";
opt.ClientId = "mvc-client";
opt.ResponseType = "code id_token";
opt.SaveTokens = true;
opt.ClientSecret = "MVCSecret";
opt.ClaimsIssuer = "https://identity.demo.local";
opt.RequireHttpsMetadata = false;
});
...
TL,DR: A micro-service application host on docker, which included IdentityServer, MVC, Nginx. They all run internal and only can be access via nginx proxy. The host name also configure to virtual host names - which make more sense.
Okay here is the problem, when I access to a protected api of MVC, it redirect me to identity server (identity.demo.local) to login, but after I login success, it should redirect me to the mvc, but it did not. After research, I figure out the reason that after login, the identity redirect me to the origin site with the cookies contain authentication info, but the redirect uri is not secured, it's http://frontend.demo.local instead of https. I'm not sure how this property is configured ( I try to update the nginx.conf but nothing change). And it still work correctly when I run by visual studio, without docker.
Any help is appreciated.

Reverse proxy of multiple container

I have 2 API containers (docker) running on port 10000 and 10003. I want to reverse proxy both of them so the API can be called from a single port which is port 80. I am trying to use NGINX to do that and this is my nginx configuration file:
worker_processes 1;
events { worker_connections 1024; }
http {
server {
listen 80;
server_name container1;
location / {
proxy_pass http://10.10.10.50:10003;
}
}
server {
listen 80;
server_name container2;
location / {
proxy_pass http://10.10.10.50:10000;
}
}
}
I found that it is only working on the container 1 and if there is a request for container 2, it will generate 404 not found warning because the request go to the container 1 instead of container 2.
Finally, I found a solution using NGINX. All I need to do is to create a new NGINX container then reconfigure the url of my 2 API container. The configuration file that I wrote looks like this:
worker_processes auto;
events { worker_connections 1024; }
http {
upstream container1 {
server 10.10.10.50:10003;
}
upstream container2 {
server 10.10.10.50:10000;
}
server {
listen 80;
location /container1/ {
proxy_pass http://container1/;
}
location /container2/ {
proxy_pass http://container2/;
}
}
}
Now, I can make requests for both API containers by using port 80 as it will be re-routed from the port into the designated port (reverse-proxy).

Rails 6 Production ActionCable Error during WebSocket handshake

WebSocket connection to 'ws://my-ec2/cable' failed:
Error during WebSocket handshake: Unexpected response code: 404
Guys, I saw this (old) issue a lot here, so it may seem like it is duplicated. But in my case, I try very hard to fix this error but I can't.
I also follow this correction: https://stackoverflow.com/a/55715218/8478892 but no success.
My nginx.conf:
upstream puma {
server unix:///home/ubuntu/apps/my_app/shared/tmp/sockets/my_app-puma.sock;
}
server {
listen 80 default_server deferred;
# If you're planning on using SSL (which you should), you can also go ahead and fill out the following server_name variable:
# server_name example.com;
# Don't forget to update these, too
root /home/ubuntu/apps/my_app/current/public;
access_log /var/log/nginx/nginx.access.log;
error_log /var/log/nginx/nginx.error.log info;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
try_files $uri/index.html $uri #puma;
location #puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://puma;
}
location /cable {
proxy_pass http://puma;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_request_headers on;
proxy_buffering off;
proxy_redirect off;
break;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 10M;
keepalive_timeout 10;
}
And in my cable.yml:
production:
url: redis://http://my-ec2.com:6379
local: &local
url: redis://localhost:6379
development: *local
test: *local
And my environments/production.rb:
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.active_storage.service = :local
config.action_cable.mount_path = '/cable'
config.action_cable.url = 'ws://my-ec2/cable'
config.action_cable.allow_same_origin_as_host = true
config.action_cable.allowed_request_origins = ["*"]
config.log_level = :debug
config.log_tags = [ :request_id ]
config.action_mailer.perform_caching = false
config.i18n.fallbacks = true
config.active_support.deprecation = :notify
config.log_formatter = ::Logger::Formatter.new
if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
config.active_record.dump_schema_after_migration = false
end
Thought of the day for those who are having this problem:
Setting up ActionCable en localhost had already been a good fight, but setting up in production is an entire war.
Do you have redis installed on your machine or is it a docker container? I think you are using it with sidekiq, and do you have any sidekiq/redis initializer file under /config/initializers?
In cable.yml it should be
production:
url: redis://redis:6379/0
After a few days, I managed to solve this problem myself.
The main reason for my error is that in my environments / production.rb file, I was saying that the actioncable endpoint was the public ip of my ec2. But in reality you should put localhost. I used the same configuration I have in development.rb:
production.rb before:
...
config.action_cable.mount_path = '/cable'
config.action_cable.url = 'ws://my_ec2/cable'
config.action_cable.allow_same_origin_as_host = true
config.action_cable.allowed_request_origins = ["*"]
...
production.rb after:
...
config.action_cable.disable_request_forgery_protection = true
config.action_cable.url = "ws://localhost:3000/cable"
config.action_cable.allowed_request_origins = [/http:\/\/*/, /https:\/\/*/]
config.action_cable.allowed_request_origins = /(\.dev$)|^localhost$/
...

Socket.IO-Client-Swift does not connect to nodejs socket.io server

I need your help about Websocket with iOS.
I am trying to connect to websocket server using Node.js from iOS client using Socket.IO-Client-Swift, but it seems Node.js websocket server does not recognize access from iOS client.
/etc/nginx/conf.d/rsp.arakaki.app.conf
(Configuration file for nginx)
Websocket server is running on port 3000, so requests to /socket.io/ will be proxied to upstream websocket (server localhost:3000;).
upstream php-fpm {
server localhost:9000;
}
upstream websocket {
server localhost:3000;
}
server {
server_name rsp.arakaki.app;
listen 80;
return 301 https://$host$request_uri;
}
server {
server_name rsp.arakaki.app;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/rsp.arakaki.app/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rsp.arakaki.app/privkey.pem;
root /var/www/rsp.arakaki.app/webroot;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location /socket.io/ {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass php-fpm;
fastcgi_index index.php;
fastcgi_intercept_errors on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
/var/www/rsp.arakaki.app/websocket/src/server.js
(Source file for websocket server)
I suppose that websocket server logs some messages when websocket client successfully connected to the server. But I can only see the messages when I use Web client. I cannot see any message when I use iOS client.
const fs = require("fs");
const express = require("express");
const app = express();
const http = require("http");
const server = http.createServer(app);
const io = require("socket.io")(server);
io.on("connection", (socket) => {
console.log("a user connected");
socket.on("disconnecting", (reason) => {
console.log("user disconnecting", { reason });
});
socket.on("disconnect", (reason) => {
console.log("user disconnected", { reason });
});
});
server.listen(3000, () => {
console.log("server running...");
});
MainViewController.swift
(iOS websocket client)
Websocket server logs nothing with this code.
I think it is because of iOS client since I succeeded to connect from web client.
I have no idea.
import UIKit
import SocketIO
class MainViewController: UIViewController {
var manager: SocketManager!
var socket: SocketIOClient!
override func viewDidLoad() {
super.viewDidLoad()
manager = SocketManager(socketURL: URL(string: "https://rsp.arakaki.app/")!, config: [.log(true), .forceWebsockets(true)])
socket = manager?.defaultSocket
socket.on(clientEvent: .connect) { (data, ack) in
print("socket connected")
self.socket.emit("join", ["hoge"])
}
socket.on(clientEvent: .error, callback: { (data, ack) in
print("socket error")
})
socket.connect(timeoutAfter: 3, withHandler: {
print("socket timeout")
})
}
}
websocket.php
(Web websocket client)
This script works well. Websocket server logs as connected.
<script src="https://rsp.arakaki.app/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
Environment
CentOS 7
Nginx (1.18.0)
Node.js (14.15.3)
socket.io (3.0.4)
express (4.17.1)
Swift 5
Socket.IO-Client-Swift (15.2.0)
Xcode (12.3)
Thank you for your interest. Any comments are welcome. Please help me.
Socket.IO-Client-Swift could connect to server after I downgraded socket.io library for nodejs server to version 2.0.4. Thank you.
Your "Socket.IO-Client-Swift" version is 15.2, it is not compatible with socket.io server's 3.0 version.
"The client now supports socket.io 3 servers." is written in "Upgrading from v15 to v16" guide: https://nuclearace.github.io/Socket.IO-Client-Swift/15to16.html
You must use v16 minimum for server 3.0 version, but already not released. Probably it is coming soon.
So using the socket.io server's 2 version it is good solution for as now.
You can look version tags for ios client at the below link:
https://github.com/socketio/socket.io-client-swift/tags

Resources