7 Replies Latest reply on May 23, 2020 4:37 PM by Olafur T

    Generate Swagger doc with tomcat behind a proxy server: how to set scheme

    Olafur T

      Hi,

       

      So I'm having some trouble getting the Swagger/Openapi 2.0 document to list the correct scheme.

       

      ORDS is running on Tomcat in a DMZ and I have NGINX in front handling all the TLS handshakes. NGINX uses HTTP in its requests to Tomcat and answers only to HTTPS to all external requests.

       

      When ORDS generates the Swagger JSON, it will show only

       

      I've been trying to change it (including using NGINX's subfilter clause to simple find/replace the string) but I want to find a non-quickfix for the problem.

       

      Right now, NGINX is sending the following to the Tomcat server.

      • x-forwarded-ssl = on
      • x-forwarded-protocol = https
      • x-scheme = https
      • x-forwarded-proto = https
      • x-https-protocol = TLSv1.2

       

      But the ORDS+Tomcat seems to ignore it. Is there any way to make the Swagger generated documentation to listen to a request header that will make it produce a "schemes":["https"] instead?

       

      I do have "security.verifySSL" = false, since these two machines live in their own little VLAN with only one port open between them.

       

      Regards

      Oli

        • 1. Re: Generate Swagger doc with tomcat behind a proxy server: how to set scheme
          EJEGYED

          The schemes should change to HTTPS if you have your Tomcat server running HTTPS instead of HTTP.  The Tomcat servers can just be set up with a self-signed certificate if needed to get HTTPS running and this would improve your overall security anyway.  With your current setup, anyone monitoring your network traffic between your NGINX server and your Tomcat servers would be able to see the unencrypted traffic (credit card info, OAuth client/secrets/tokens, usernames/passwords, etc.).  Using HTTPS at every step makes the application more secure and should resolve the issue you are seeing.

          • 2. Re: Generate Swagger doc with tomcat behind a proxy server: how to set scheme
            Olafur T

            There is no one else on that VPN. The nginx server has a single port opened to the Tomcat via a closed VPN.

             

            I know I can use a self-signed certificate, but my question is how can I get the metadata part of ORDS to read the headers x-forwarded-protocol, x-scheme or x-forwarded-proto or get the correct header if there one.

             

            Olafur

            • 3. Re: Generate Swagger doc with tomcat behind a proxy server: how to set scheme
              EJEGYED

              So this is may not be the most efficient way, but I can't find any configuration within ORDS to allow HTTP and HTTPS to appear in the schemes, so I built my own REST endpoint that gets the original ORDS generated Swagger documentation, then modifies it to have both schemes.  The magic is happening in the open-api-catalog handler defined below.  The other templates/handlers are just other sample endpoints so they will show in the documentation.

               

              Hitting http://hostname/ords/api-demo/open-api-catalog/v1/ returns the ORDS generated, but hitting http://hostname/ords/api-demo/v1/open-api-catalog returns the customized Swagger documentation.

               

              I am using Oracle 19 and APEX 19.2 so I have JSON_OBJECT_T and APEX_WEB_SERVICE.MAKE_REST_REQUEST available to me which made the code much simpler.  You may need to modify it slightly if you have a module with a large number of templates/handlers to convert l_response to a clob, then call htp.p multiple times.

               

               

              BEGIN
                  ORDS.ENABLE_SCHEMA (p_enabled               => TRUE,
                                      p_url_mapping_type      => 'BASE_PATH',
                                      p_url_mapping_pattern   => 'api-demo',
                                      p_auto_rest_auth        => FALSE);
              
                  ORDS.DEFINE_MODULE (p_module_name => 'v1', p_base_path => 'v1/');
              
                  ORDS.define_template (p_module_name => 'v1', p_pattern => 'numbers');
              
                  ORDS.define_handler (p_module_name   => 'v1',
                                       p_pattern       => 'numbers',
                                       p_method        => 'GET',
                                       p_source_type   => ORDS.source_type_collection_feed,
                                       p_source        => 'SELECT rownum from dual connect by level <= 10');
              
              
                  ORDS.define_template (p_module_name => 'v1', p_pattern => 'nothing');
              
                  ORDS.define_handler (p_module_name   => 'v1',
                                       p_pattern       => 'nothing',
                                       p_method        => 'POST',
                                       p_source_type   => ORDS.source_type_plsql,
                                       p_source        => 'begin null; end;');
              
                  ORDS.define_template (p_module_name => 'v1', p_pattern => 'open-api-catalog');
              
                  ORDS.define_handler (
                      p_module_name   => 'v1',
                      p_pattern       => 'open-api-catalog',
                      p_method        => 'GET',
                      p_source_type   => ORDS.source_type_plsql,
                      p_source        =>
                          '
                           DECLARE
                             l_server     varchar2(100) := OWA_UTIL.get_cgi_env (''SERVER_NAME'');
                             l_port       varchar2(100) := OWA_UTIL.get_cgi_env (''SERVER_PORT'');
                             l_script     varchar2(100) := OWA_UTIL.get_cgi_env (''SCRIPT_NAME'');
                             l_response   json_object_t;
                             l_paths      json_object_t;
                           BEGIN
                             l_response :=
                                 json_object_t (apex_web_service.make_rest_request (
                                                     p_url           => l_server || '':'' || l_port || substr(l_script,1,instr(l_script,''/'',-1) - 1) || ''/open-api-catalog'' || substr(l_script,instr(l_script,''/'',-1)) || ''/'',
                                                     p_http_method   => ''GET''));
              
                              --put HTTP and HTTPS in the schemes
                              l_response.put (''schemes'', json_array_t (''["http","https"]''));
              
                              --remove the non-ORDS-generated open-api-catalog
                              l_paths := l_response.get_object (''paths'');
                              l_paths.remove (''/open-api-catalog'');
                              l_response.put (''paths'', l_paths);
              
                             OWA_UTIL.mime_header(''application/json'');
                             htp.p (l_response.stringify);
                           END;');
                  COMMIT;
              END;
              /
              
              • 4. Re: Generate Swagger doc with tomcat behind a proxy server: how to set scheme
                Olafur T

                Wow,

                That is clever.

                 

                I can see clearly that this will work, would prefer an Oracle solution, but will look into implementing this if I can't get an Tomcat/ORDS solution.

                 

                Never crossed my mind to simply consume the Swagger Doc and change it.

                 

                My largest ORDS setup has around 920 handlers. Smallest has 162. Each module has around 10 to 70 handlers.These are all on different slices on an M7 Supercluster (on premise). Around 14 different schemas on 4 different PDB's .

                 

                Would really like a formal solution rather then a fix like this (even thought it is really good)

                 

                Regards

                Olafur

                 

                ps. I'm getting > 1 100k hits per hour. Keeping tomcat without https is a minor fragment, but really counts when dealing with these serious amounts. the very small overhead of using tomcat using TLS matters. and since its in a closed environment, I don't care. ..

                • 5. Re: Generate Swagger doc with tomcat behind a proxy server: how to set scheme
                  EJEGYED

                  I agree that a formal solution from Oracle would be best, but I can not find one.You'll probably want something like the code below which converts the JSON_OBJECT_T to a CLOB then print the clob since you have very large modules.

                   

                  HTTPS may cause a bit more CPU usage on the Tomcat server, but may even see performance benefits if HTTP/2 is enabled which is only available when using HTTPS.  When HTTP/3 is finalized and both Tomcat and NGINX support it, that should have even greater performance improvements (although even more CPU usage), but again will only be available over HTTPS.  It might be worth testing HTTPS on the Tomcat server to see if it truly does impact performance of the end user.

                   

                   

                  BEGIN
                      ORDS.ENABLE_SCHEMA (p_enabled               => TRUE,
                                          p_url_mapping_type      => 'BASE_PATH',
                                          p_url_mapping_pattern   => 'api-demo',
                                          p_auto_rest_auth        => FALSE);
                  
                  
                      ORDS.DEFINE_MODULE (p_module_name => 'v1', p_base_path => 'v1/');
                  
                  
                      FOR i IN 1 .. 1000
                      LOOP
                          ORDS.define_template (p_module_name => 'v1', p_pattern => 'numbers' || i);
                  
                  
                          ORDS.define_handler (p_module_name   => 'v1',
                                               p_pattern       => 'numbers' || i,
                                               p_method        => 'GET',
                                               p_source_type   => ORDS.source_type_collection_feed,
                                               p_source        => 'SELECT rownum from dual connect by level <= ' || i);
                      END LOOP;
                  
                  
                      ORDS.define_template (p_module_name => 'v1', p_pattern => 'nothing');
                  
                  
                      ORDS.define_handler (p_module_name   => 'v1',
                                           p_pattern       => 'nothing',
                                           p_method        => 'POST',
                                           p_source_type   => ORDS.source_type_plsql,
                                           p_source        => 'begin null; end;');
                  
                  
                      ORDS.define_template (p_module_name => 'v1', p_pattern => 'open-api-catalog');
                  
                  
                      ORDS.define_handler (
                          p_module_name   => 'v1',
                          p_pattern       => 'open-api-catalog',
                          p_method        => 'GET',
                          p_source_type   => ORDS.source_type_plsql,
                          p_source        =>
                              '
                               DECLARE
                                 l_server varchar2(100) := OWA_UTIL.get_cgi_env (''SERVER_NAME'');
                                 l_port   varchar2(100) := OWA_UTIL.get_cgi_env (''SERVER_PORT'');
                                 l_script varchar2(100) := OWA_UTIL.get_cgi_env (''SCRIPT_NAME'');
                                 l_response   json_object_t;
                                 l_response_c clob;
                                 l_paths      json_object_t;
                                 l_amt        number := 4096;
                                 l_off        number := 1;
                                 l_str        varchar2(32767);
                               BEGIN
                                 l_response :=
                                     json_object_t (apex_web_service.make_rest_request (
                                                         p_url           => l_server || '':'' || l_port || substr(l_script,1,instr(l_script,''/'',-1) - 1) || ''/open-api-catalog'' || substr(l_script,instr(l_script,''/'',-1)) || ''/'',
                                                         p_http_method   => ''GET''));
                  
                  
                                  --put HTTP and HTTPS in the schemes
                                  l_response.put (''schemes'', json_array_t (''["http","https"]''));
                  
                  
                                  --remove the non-ORDS-generated open-api-catalog
                                  l_paths := l_response.get_object (''paths'');
                                  l_paths.remove (''/open-api-catalog'');
                                  l_response.put (''paths'', l_paths);
                                  
                                  l_response_c := l_response.to_clob;
                  
                  
                                  OWA_UTIL.mime_header(''application/json'');
                                  BEGIN
                                      LOOP
                                          DBMS_LOB.read (l_response_c,
                                                         l_amt,
                                                         l_off,
                                                         l_str);
                                          HTP.prn (l_str);
                                          l_off := l_off + l_amt;
                                      END LOOP;
                                  EXCEPTION
                                      WHEN NO_DATA_FOUND
                                      THEN
                                          NULL;
                                  END;
                               END;');
                      COMMIT;
                  END;
                  /
                  
                  
                  • 6. Re: Generate Swagger doc with tomcat behind a proxy server: how to set scheme
                    Olafur T

                    Actually the main problem is that the produced OpenAPI doesn't send a content-type header. If it would send a response header "Content-Type: application/json" or "Content-Type: application/json;charset=UTF-8" I could easily fix this using NGINX

                     

                    Open api catalog: no content type

                    [user ~]$ curl -I https://myweb/ords/alias/open-api-catalog/testme/
                    HTTP/1.1 200 
                    Server: nginx
                    Date: Sat, 23 May 2020 12:18:23 GMT
                    Connection: keep-alive
                    Keep-Alive: timeout=60
                    Access-Control-Allow-Origin: *

                     

                    Ords login, has content type.

                    [user ~]$ curl -I https://myweb/ords/
                    HTTP/1.1 302 
                    Server: nginx
                    Date: Sat, 23 May 2020 12:20:22 GMT
                    Content-Type: text/html;charset=UTF-8
                    Connection: keep-alive
                    Keep-Alive: timeout=60
                    X-Content-Type-Options: nosniff
                    X-Xss-Protection: 1; mode=block
                    Cache-Control: no-store
                    Pragma: no-cache
                    Expires: Sun, 27 Jul 1997 13:00:00 GMT
                    Set-Cookie: ORA_WWV_USER_61905964550085=ORA_WWV-7bUR-Lt2g69ykiI0tuuMEryu; path=/ords/; samesite=none; secure; HttpOnly
                    Location: https://myweb.com/ords/f?p=4550:1:5078514069386:::::
                    Access-Control-Allow-Origin: *
                    [user ~]$ 
                    

                     

                     

                    It it returned the content type this is easily fixable in nginx. Just put this in the ords location:

                     

                    sub_filter_types application/json;
                    sub_filter '["http"]' '["https"]';

                     

                    Regards

                    Olafur

                     

                    ps. If I call it on the Tomcat server it serves even less information

                    $ curl -I http://localhost:8000/ords/alias/open-api-catalog/testme/
                    HTTP/1.1 200 
                    Transfer-Encoding: chunked
                    Date: Sat, 23 May 2020 12:33:23 GMT
                    
                    • 7. Re: Generate Swagger doc with tomcat behind a proxy server: how to set scheme
                      Olafur T

                      I ended up simply adding a self signed certificate. Spent too much time trying to get around this. Simply made the certificate "-validity 9999", so I don't have to vorry about renewals.

                       

                      Olafur