Browse Source

Merge pull request #1018 from abutcher/secrets

Clean up idempotency issues with session secrets.
Brenton Leanhardt 9 years ago
parent
commit
31a18b4e60

+ 13 - 6
filter_plugins/oo_filters.py

@@ -8,12 +8,11 @@ Custom filters for use in openshift-ansible
 from ansible import errors
 from operator import itemgetter
 import OpenSSL.crypto
-import os.path
+import os
 import pdb
 import re
 import json
 
-
 class FilterModule(object):
     ''' Custom ansible filters '''
 
@@ -366,9 +365,6 @@ class FilterModule(object):
                            "keyfile": "/etc/origin/master/named_certificates/custom2.key",
                            "names": [ "some-hostname.com" ] }]
         '''
-        if not issubclass(type(certificates), list):
-            raise errors.AnsibleFilterError("|failed expects certificates is a list")
-
         if not issubclass(type(named_certs_dir), unicode):
             raise errors.AnsibleFilterError("|failed expects named_certs_dir is unicode")
 
@@ -468,6 +464,16 @@ class FilterModule(object):
                 pass
         return clusters
 
+    @staticmethod
+    def oo_generate_secret(num_bytes):
+        ''' generate a session secret '''
+
+        if not issubclass(type(num_bytes), int):
+            raise errors.AnsibleFilterError("|failed expects num_bytes is int")
+
+        secret = os.urandom(num_bytes)
+        return secret.encode('base-64').strip()
+
     def filters(self):
         ''' returns a mapping of filters to methods '''
         return {
@@ -486,5 +492,6 @@ class FilterModule(object):
             "oo_parse_heat_stack_outputs": self.oo_parse_heat_stack_outputs,
             "oo_parse_named_certificates": self.oo_parse_named_certificates,
             "oo_haproxy_backend_masters": self.oo_haproxy_backend_masters,
-            "oo_pretty_print_cluster": self.oo_pretty_print_cluster
+            "oo_pretty_print_cluster": self.oo_pretty_print_cluster,
+            "oo_generate_secret": self.oo_generate_secret
         }

+ 0 - 1
filter_plugins/openshift_master.py

@@ -463,7 +463,6 @@ class FilterModule(object):
         IdentityProviderBase.validate_idp_list(idp_list)
         return yaml.safe_dump([idp.to_dict() for idp in idp_list], default_flow_style=False)
 
-
     def filters(self):
         ''' returns a mapping of filters to methods '''
         return {"translate_idps": self.translate_idps}

+ 26 - 23
playbooks/common/openshift-master/config.yml

@@ -236,29 +236,32 @@
   - role: haproxy
     when: groups.oo_masters_to_config | length > 1
 
-- name: Generate master session keys
+- name: Check for cached session secrets
   hosts: oo_first_master
+  roles:
+  - role: openshift_facts
+  post_tasks:
+  - openshift_facts:
+      role: master
+      local_facts:
+          session_auth_secrets: "{{ openshift_master_session_auth_secrets | default(openshift.master.session_auth_secrets | default(None)) }}"
+          session_encryption_secrets: "{{ openshift_master_session_encryption_secrets | default(openshift.master.session_encryption_secrets | default(None)) }}"
+
+- name: Generate master session secrets
+  hosts: oo_first_master
+  vars:
+    g_session_secrets_present: "{{ (openshift.master.session_auth_secrets | default([]) and openshift.master.session_encryption_secrets | default([])) | length > 0 }}"
+    g_session_auth_secrets: "{{ [ 24 | oo_generate_secret ] }}"
+    g_session_encryption_secrets: "{{ [ 24 | oo_generate_secret ] }}"
+  roles:
+  - role: openshift_facts
   tasks:
-  - fail:
-      msg: "Both openshift_master_session_auth_secrets and openshift_master_session_encryption_secrets must be provided if either variable is set"
-    when: (openshift_master_session_auth_secrets is defined and openshift_master_session_encryption_secrets is not defined) or (openshift_master_session_encryption_secrets is defined and openshift_master_session_auth_secrets is not defined)
-  - fail:
-      msg: "openshift_master_session_auth_secrets and openshift_master_encryption_secrets must be equal length"
-    when: (openshift_master_session_auth_secrets is defined and openshift_master_session_encryption_secrets is defined) and (openshift_master_session_auth_secrets | length != openshift_master_session_encryption_secrets | length)
-  - name: Install OpenSSL package
-    action: "{{ ansible_pkg_mgr }} name=openssl state=present"
-    when: not openshift.common.is_atomic | bool
-  - name: Generate session authentication key
-    command: /usr/bin/openssl rand -base64 24
-    register: session_auth_output
-    when: openshift_master_session_auth_secrets is undefined
-  - name: Generate session encryption key
-    command: /usr/bin/openssl rand -base64 24
-    register: session_encryption_output
-    when: openshift_master_session_encryption_secrets is undefined
-  - set_fact:
-      session_auth_secret: "{{ openshift_master_session_auth_secrets | default([session_auth_output.stdout]) }}"
-      session_encryption_secret: "{{ openshift_master_session_encryption_secrets | default([session_encryption_output.stdout]) }}"
+  - openshift_facts:
+      role: master
+      local_facts:
+        session_auth_secrets: "{{ g_session_auth_secrets }}"
+        session_encryption_secrets: "{{ g_session_encryption_secrets }}"
+    when: not g_session_secrets_present | bool
 
 - name: Parse named certificates
   hosts: localhost
@@ -314,8 +317,8 @@
     sync_tmpdir: "{{ hostvars.localhost.g_master_mktemp.stdout }}"
     openshift_master_ha: "{{ groups.oo_masters_to_config | length > 1 }}"
     openshift_master_count: "{{ groups.oo_masters_to_config | length }}"
-    openshift_master_session_auth_secrets: "{{ hostvars[groups['oo_first_master'][0]]['session_auth_secret'] }}"
-    openshift_master_session_encryption_secrets: "{{ hostvars[groups['oo_first_master'][0]]['session_encryption_secret'] }}"
+    openshift_master_session_auth_secrets: "{{ hostvars[groups.oo_first_master.0].openshift.master.session_auth_secrets }}"
+    openshift_master_session_encryption_secrets: "{{ hostvars[groups.oo_first_master.0].openshift.master.session_encryption_secrets }}"
   pre_tasks:
   - name: Ensure certificate directory exists
     file:

+ 66 - 1
roles/openshift_facts/library/openshift_facts.py

@@ -1043,6 +1043,7 @@ class OpenShiftFacts(object):
             facts (dict): facts for the host
 
         Args:
+            module (AnsibleModule): an AnsibleModule object
             role (str): role for setting local facts
             filename (str): local facts file to use
             local_facts (dict): local facts to set
@@ -1263,14 +1264,78 @@ class OpenShiftFacts(object):
                 del facts[key]
 
         if new_local_facts != local_facts:
+            self.validate_local_facts(new_local_facts)
             changed = True
-
             if not module.check_mode:
                 save_local_facts(self.filename, new_local_facts)
 
         self.changed = changed
         return new_local_facts
 
+    def validate_local_facts(self, facts=None):
+        """ Validate local facts
+
+            Args:
+                facts (dict): local facts to validate
+        """
+        invalid_facts = dict()
+        invalid_facts = self.validate_master_facts(facts, invalid_facts)
+        if invalid_facts:
+            msg = 'Invalid facts detected:\n'
+            for key in invalid_facts.keys():
+                msg += '{0}: {1}\n'.format(key, invalid_facts[key])
+            module.fail_json(msg=msg,
+                             changed=self.changed)
+
+    # disabling pylint errors for line-too-long since we're dealing
+    # with best effort reduction of error messages here.
+    # disabling errors for too-many-branches since we require checking
+    # many conditions.
+    # pylint: disable=line-too-long, too-many-branches
+    @staticmethod
+    def validate_master_facts(facts, invalid_facts):
+        """ Validate master facts
+
+            Args:
+                facts (dict): local facts to validate
+                invalid_facts (dict): collected invalid_facts
+
+            Returns:
+                dict: Invalid facts
+        """
+        if 'master' in facts:
+            # openshift.master.session_auth_secrets
+            if 'session_auth_secrets' in facts['master']:
+                session_auth_secrets = facts['master']['session_auth_secrets']
+                if not issubclass(type(session_auth_secrets), list):
+                    invalid_facts['session_auth_secrets'] = 'Expects session_auth_secrets is a list.'
+                elif 'session_encryption_secrets' not in facts['master']:
+                    invalid_facts['session_auth_secrets'] = ('openshift_master_session_encryption secrets must be set '
+                                                             'if openshift_master_session_auth_secrets is provided.')
+                elif len(session_auth_secrets) != len(facts['master']['session_encryption_secrets']):
+                    invalid_facts['session_auth_secrets'] = ('openshift_master_session_auth_secrets and '
+                                                             'openshift_master_session_encryption_secrets must be '
+                                                             'equal length.')
+                else:
+                    for secret in session_auth_secrets:
+                        if len(secret) < 32:
+                            invalid_facts['session_auth_secrets'] = ('Invalid secret in session_auth_secrets. '
+                                                                     'Secrets must be at least 32 characters in length.')
+            # openshift.master.session_encryption_secrets
+            if 'session_encryption_secrets' in facts['master']:
+                session_encryption_secrets = facts['master']['session_encryption_secrets']
+                if not issubclass(type(session_encryption_secrets), list):
+                    invalid_facts['session_encryption_secrets'] = 'Expects session_encryption_secrets is a list.'
+                elif 'session_auth_secrets' not in facts['master']:
+                    invalid_facts['session_encryption_secrets'] = ('openshift_master_session_auth_secrets must be '
+                                                                   'set if openshift_master_session_encryption_secrets '
+                                                                   'is provided.')
+                else:
+                    for secret in session_encryption_secrets:
+                        if len(secret) not in [16, 24, 32]:
+                            invalid_facts['session_encryption_secrets'] = ('Invalid secret in session_encryption_secrets. '
+                                                                           'Secrets must be 16, 24, or 32 characters in length.')
+        return invalid_facts
 
 def main():
     """ main """

+ 2 - 3
roles/openshift_master/tasks/main.yml

@@ -9,7 +9,6 @@
       Invalid OAuth grant method: {{ openshift_master_oauth_grant_method }}
   when: openshift_master_oauth_grant_method is defined and openshift_master_oauth_grant_method not in openshift_master_valid_grant_methods
 
-
 # HA Variable Validation
 - fail:
     msg: "openshift_master_cluster_method must be set to either 'native' or 'pacemaker' for multi-master installations"
@@ -55,9 +54,9 @@
       portal_net: "{{ openshift_master_portal_net | default(None) }}"
       session_max_seconds: "{{ openshift_master_session_max_seconds | default(None) }}"
       session_name: "{{ openshift_master_session_name | default(None) }}"
+      session_secrets_file: "{{ openshift_master_session_secrets_file | default(None) }}"
       session_auth_secrets: "{{ openshift_master_session_auth_secrets | default(None) }}"
       session_encryption_secrets: "{{ openshift_master_session_encryption_secrets | default(None) }}"
-      session_secrets_file: "{{ openshift_master_session_secrets_file | default(None) }}"
       access_token_max_seconds: "{{ openshift_master_access_token_max_seconds | default(None) }}"
       auth_token_max_seconds: "{{ openshift_master_auth_token_max_seconds | default(None) }}"
       identity_providers: "{{ openshift_master_identity_providers | default(None) }}"
@@ -221,7 +220,7 @@
   template:
     dest: "{{ openshift.master.session_secrets_file }}"
     src: sessionSecretsFile.yaml.v1.j2
-    force: no
+  when: openshift.master.session_auth_secrets is defined and openshift.master.session_encryption_secrets is defined
   notify:
   - restart master
   - restart master api

+ 2 - 0
roles/openshift_master/templates/master.yaml.v1.j2

@@ -127,7 +127,9 @@ oauthConfig:
   sessionConfig:
     sessionMaxAgeSeconds: {{ openshift.master.session_max_seconds }}
     sessionName: {{ openshift.master.session_name }}
+{% if openshift.master.session_auth_secrets is defined and openshift.master.session_encryption_secrets is defined %}
     sessionSecretsFile: {{ openshift.master.session_secrets_file }}
+{% endif %}
   tokenConfig:
     accessTokenMaxAgeSeconds: {{ openshift.master.access_token_max_seconds }}
     authorizeTokenMaxAgeSeconds: {{ openshift.master.auth_token_max_seconds }}

+ 3 - 3
roles/openshift_master/templates/sessionSecretsFile.yaml.v1.j2

@@ -1,7 +1,7 @@
 apiVersion: v1
 kind: SessionSecrets
 secrets:
-{% for secret in openshift_master_session_auth_secrets %}
-- authentication: "{{ openshift_master_session_auth_secrets[loop.index0] }}"
-  encryption: "{{ openshift_master_session_encryption_secrets[loop.index0] }}"
+{% for secret in openshift.master.session_auth_secrets %}
+- authentication: "{{ openshift.master.session_auth_secrets[loop.index0] }}"
+  encryption: "{{ openshift.master.session_encryption_secrets[loop.index0] }}"
 {% endfor %}