Repository: cloud-custodian/cloud-custodian

Issue:
Azure - Add Machine Decryption Key as an App Setting
This will prevent an unnecessary recycle of the Linux app container for Function deployment.


Discussion:

Resolving patch:
diff --git a/tools/c7n_azure/c7n_azure/functionapp_utils.py b/tools/c7n_azure/c7n_azure/functionapp_utils.py
--- a/tools/c7n_azure/c7n_azure/functionapp_utils.py
+++ b/tools/c7n_azure/c7n_azure/functionapp_utils.py
@@ -1,4 +1,20 @@
+# Copyright 2015-2018 Capital One Services, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
 import logging
+from binascii import hexlify
 
 from azure.mgmt.web.models import (Site, SiteConfig, NameValuePair)
 from c7n_azure.session import Session
@@ -12,6 +28,11 @@ def __init__(self):
         self.local_session = local_session(Session)
         self.log = logging.getLogger('custodian.azure.function_app_utils')
 
+    @staticmethod
+    def generate_machine_decryption_key():
+        # randomly generated decryption key for Functions key
+        return str(hexlify(os.urandom(32)).decode()).upper()
+
     def deploy_webapp(self, app_name, group_name, service_plan, storage_account_name):
         self.log.info("Deploying Function App %s (%s) in group %s" %
                       (app_name, service_plan.location, group_name))
@@ -38,6 +59,9 @@ def deploy_webapp(self, app_name, group_name, service_plan, storage_account_name
         site_config.app_settings.append(NameValuePair('FUNCTIONS_EXTENSION_VERSION',
                                                       CONST_FUNCTIONS_EXT_VERSION))
         site_config.app_settings.append(NameValuePair('FUNCTIONS_WORKER_RUNTIME', 'python'))
+        site_config.app_settings.append(
+            NameValuePair('MACHINEKEY_DecryptionKey',
+                          FunctionAppUtilities.generate_machine_decryption_key()))
 
         #: :type: azure.mgmt.web.WebSiteManagementClient
         web_client = self.local_session.client('azure.mgmt.web.WebSiteManagementClient')
@@ -64,7 +88,7 @@ def get_application_insights_key(self, resource_group_name, application_insights
 
         try:
             app_insights = insights_client.components.get(resource_group_name,
-                                                      application_insights_name)
+                                                          application_insights_name)
             return app_insights.instrumentation_key
 
         except Exception:
diff --git a/tools/c7n_azure/c7n_azure/policy.py b/tools/c7n_azure/c7n_azure/policy.py
--- a/tools/c7n_azure/c7n_azure/policy.py
+++ b/tools/c7n_azure/c7n_azure/policy.py
@@ -12,28 +12,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import requests
-import logging
 import json
+import logging
 import time
 
-from c7n_azure.function_package import FunctionPackage
-from msrestazure.azure_exceptions import CloudError
-from c7n_azure.functionapp_utils import FunctionAppUtilities
-from c7n_azure.template_utils import TemplateUtilities
+import requests
+from azure.mgmt.eventgrid.models import (EventSubscription, EventSubscriptionFilter,
+                                         WebHookEventSubscriptionDestination)
 from c7n_azure.azure_events import AzureEvents
 from c7n_azure.constants import (CONST_DOCKER_VERSION, CONST_FUNCTIONS_EXT_VERSION,
                                  CONST_AZURE_EVENT_TRIGGER_MODE, CONST_AZURE_TIME_TRIGGER_MODE,
                                  CONST_AZURE_FUNCTION_KEY_URL)
+from c7n_azure.function_package import FunctionPackage
+from c7n_azure.functionapp_utils import FunctionAppUtilities
+from c7n_azure.template_utils import TemplateUtilities
+from msrestazure.azure_exceptions import CloudError
 
 from c7n import utils
 from c7n.actions import EventAction
 from c7n.policy import ServerlessExecutionMode, PullMode, execution
 from c7n.utils import local_session
 
-from azure.mgmt.eventgrid.models import (EventSubscription, EventSubscriptionFilter,
-                                         WebHookEventSubscriptionDestination)
-
 
 class AzureFunctionMode(ServerlessExecutionMode):
     """A policy that runs/executes in azure functions."""
@@ -102,7 +101,7 @@ def provision(self):
         archive.build(self.policy.data)
         archive.close()
 
-        self.log.info("Function package built")
+        self.log.info("Function package built, size is %dMB" % (archive.pkg.size / (1024 * 1024)))
 
         if archive.wait_for_status(self.webapp_name):
             archive.publish(self.webapp_name)
@@ -117,7 +116,8 @@ def _get_parameters(self, template_util):
 
         updated_parameters = {
             'dockerVersion': CONST_DOCKER_VERSION,
-            'functionsExtVersion': CONST_FUNCTIONS_EXT_VERSION
+            'functionsExtVersion': CONST_FUNCTIONS_EXT_VERSION,
+            'machineDecryptionKey': FunctionAppUtilities.generate_machine_decryption_key()
         }
 
         if 'mode' in data:
@@ -221,10 +221,10 @@ def _get_webhook_key(self):
             'https://management.azure.com'
             '/subscriptions/{0}/resourceGroups/{1}/'
             'providers/Microsoft.Web/sites/{2}/{3}').format(
-                self.session.subscription_id,
-                self.group_name,
-                self.webapp_name,
-                CONST_AZURE_FUNCTION_KEY_URL)
+            self.session.subscription_id,
+            self.group_name,
+            self.webapp_name,
+            CONST_AZURE_FUNCTION_KEY_URL)
 
         retrieved_key = False
 
diff --git a/tools/c7n_mailer/c7n_mailer/azure/deploy.py b/tools/c7n_mailer/c7n_mailer/azure/deploy.py
--- a/tools/c7n_mailer/c7n_mailer/azure/deploy.py
+++ b/tools/c7n_mailer/c7n_mailer/azure/deploy.py
@@ -19,6 +19,7 @@
 
 try:
     from c7n_azure.function_package import FunctionPackage
+    from c7n_azure.functionapp_utils import FunctionAppUtilities
     from c7n_azure.template_utils import TemplateUtilities
     from c7n_azure.constants import CONST_DOCKER_VERSION, CONST_FUNCTIONS_EXT_VERSION
 except ImportError:
@@ -107,6 +108,7 @@ def _get_parameters(template_util, func_config):
     func_config['storageName'] = (func_config['servicePlanName']).replace('-', '')
     func_config['dockerVersion'] = CONST_DOCKER_VERSION
     func_config['functionsExtVersion'] = CONST_FUNCTIONS_EXT_VERSION
+    func_config['machineDecryptionKey'] = FunctionAppUtilities.generate_machine_decryption_key()
 
     parameters = template_util.update_parameters(parameters, func_config)
 


Test patch:
diff --git a/tools/c7n_azure/c7n_azure/templates/dedicated_functionapp.test.parameters.json b/tools/c7n_azure/c7n_azure/templates/dedicated_functionapp.test.parameters.json
--- a/tools/c7n_azure/c7n_azure/templates/dedicated_functionapp.test.parameters.json
+++ b/tools/c7n_azure/c7n_azure/templates/dedicated_functionapp.test.parameters.json
@@ -31,6 +31,9 @@
         },
         "functionsExtVersion": {
             "value": "beta"
+        },
+        "machineDecryptionKey": {
+            "value": "DefaultKey"
         }
     }
 }
\ No newline at end of file