From 05f4a64aeda5f8a75f204754ba6dea7f83d59cf9 Mon Sep 17 00:00:00 2001
From: Michael Krotscheck <krotscheck@gmail.com>
Date: Wed, 6 May 2015 12:05:24 -0700
Subject: [PATCH] Added CORS support middleware to Ironic

This adds the CORS support middleware to Ironic, allowing a deployer
to optionally configure rules under which a javascript client may
break the single-origin policy and access the API directly.

OpenStack Spec:
   https://review.openstack.org/#/c/179866/
Oslo_Middleware Docs:
   http://docs.openstack.org/developer/oslo.middleware/cors.html
OpenStack Cloud Admin Guide Documentation:
   http://docs.openstack.org/admin-guide-cloud/cross_project_cors.html

Co-Authored-By: Devananda van der Veen <devananda.vdv@gmail.com>
Depends-on: I2deed897f8f9ef87e4a74227c4fcea9afdb151e8
Change-Id: Ic55305607e44069d893baf2a261d5fe7da777303
---
 etc/ironic/ironic.conf.sample         | 62 +++++++++++++++++++++++++++
 ironic/api/app.py                     | 11 +++++
 requirements.txt                      |  1 +
 tools/config/oslo.config.generator.rc |  2 +-
 vagrant.yaml                          | 13 +++++-
 5 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
index d50dfcf98a..a60675de7d 100644
--- a/etc/ironic/ironic.conf.sample
+++ b/etc/ironic/ironic.conf.sample
@@ -598,6 +598,68 @@
 #subprocess_timeout=10
 
 
+[cors]
+
+#
+# Options defined in oslo.middleware.cors
+#
+
+# Indicate whether this resource may be shared with the domain
+# received in the requests "origin" header. (string value)
+#allowed_origin=<None>
+
+# Indicate that the actual request can include user
+# credentials (boolean value)
+#allow_credentials=true
+
+# Indicate which headers are safe to expose to the API.
+# Defaults to HTTP Simple Headers. (list value)
+#expose_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma
+
+# Maximum cache age of CORS preflight requests. (integer
+# value)
+#max_age=3600
+
+# Indicate which methods can be used during the actual
+# request. (list value)
+#allow_methods=GET,POST,PUT,DELETE,OPTIONS
+
+# Indicate which header field names may be used during the
+# actual request. (list value)
+#allow_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma
+
+
+[cors.subdomain]
+
+#
+# Options defined in oslo.middleware.cors
+#
+
+# Indicate whether this resource may be shared with the domain
+# received in the requests "origin" header. (string value)
+#allowed_origin=<None>
+
+# Indicate that the actual request can include user
+# credentials (boolean value)
+#allow_credentials=true
+
+# Indicate which headers are safe to expose to the API.
+# Defaults to HTTP Simple Headers. (list value)
+#expose_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma
+
+# Maximum cache age of CORS preflight requests. (integer
+# value)
+#max_age=3600
+
+# Indicate which methods can be used during the actual
+# request. (list value)
+#allow_methods=GET,POST,PUT,DELETE,OPTIONS
+
+# Indicate which header field names may be used during the
+# actual request. (list value)
+#allow_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma
+
+
 [database]
 
 #
diff --git a/ironic/api/app.py b/ironic/api/app.py
index 4307c6aaeb..a9e57ba362 100644
--- a/ironic/api/app.py
+++ b/ironic/api/app.py
@@ -16,10 +16,12 @@
 #    under the License.
 
 from oslo_config import cfg
+import oslo_middleware.cors as cors_middleware
 import pecan
 
 from ironic.api import acl
 from ironic.api import config
+from ironic.api.controllers.base import Version
 from ironic.api import hooks
 from ironic.api import middleware
 from ironic.common.i18n import _
@@ -73,6 +75,15 @@ def setup_app(pecan_config=None, extra_hooks=None):
         wrap_app=middleware.ParsableErrorMiddleware,
     )
 
+    # Create a CORS wrapper, and attach ironic-specific defaults that must be
+    # included in all CORS responses.
+    app = cors_middleware.CORS(app, CONF)
+    app.set_latent(
+        allow_headers=[Version.max_string, Version.min_string, Version.string],
+        allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'],
+        expose_headers=[Version.max_string, Version.min_string, Version.string]
+    )
+
     if pecan_config.app.enable_acl:
         return acl.install(app, cfg.CONF, pecan_config.app.acl_public_routes)
 
diff --git a/requirements.txt b/requirements.txt
index e21b0e5571..7b63287d71 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -27,6 +27,7 @@ oslo.db>=2.4.1 # Apache-2.0
 oslo.rootwrap>=2.0.0 # Apache-2.0
 oslo.i18n>=1.5.0 # Apache-2.0
 oslo.log>=1.8.0 # Apache-2.0
+oslo.middleware>=2.8.0  # Apache-2.0
 oslo.policy>=0.5.0 # Apache-2.0
 oslo.serialization>=1.4.0 # Apache-2.0
 oslo.service>=0.7.0 # Apache-2.0
diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc
index e85578e3a6..2b977e866d 100644
--- a/tools/config/oslo.config.generator.rc
+++ b/tools/config/oslo.config.generator.rc
@@ -1,2 +1,2 @@
-export IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES='oslo.db oslo.messaging keystonemiddleware.auth_token oslo.concurrency oslo.policy oslo.log oslo.service.service oslo.service.periodic_task'
+export IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES='oslo.db oslo.messaging oslo.middleware.cors keystonemiddleware.auth_token oslo.concurrency oslo.policy oslo.log oslo.service.service oslo.service.periodic_task'
 export IRONIC_CONFIG_GENERATOR_EXTRA_MODULES=
diff --git a/vagrant.yaml b/vagrant.yaml
index 0d227a4fef..055a4a9dc6 100644
--- a/vagrant.yaml
+++ b/vagrant.yaml
@@ -124,6 +124,14 @@
             section: 'DEFAULT',
             option: 'pecan_debug', value: 'true'
           }
+        - {
+            section: 'DEFAULT',
+            option: 'verbose', value: 'true'
+          }
+        - {
+            section: 'DEFAULT',
+            option: 'debug', value: 'true'
+          }
         - {
             section: 'oslo_messaging_rabbit',
             option: 'rabbit_host', value: "{{ip}}"
@@ -136,7 +144,10 @@
             section: 'oslo_messaging_rabbit',
             option: 'rabbit_password', value: "ironic"
           }
-
+        - { # CORS Domain For Ironic-Webclient's dev server.
+            section: 'cors',
+            option: 'allowed_origin', value: "http://localhost:8000"
+          }
 
   #############################################################################
   # Handlers