diff options
| author | Jason DeTiberus <jdetiber@redhat.com> | 2015-11-16 16:01:54 -0500 | 
|---|---|---|
| committer | Jason DeTiberus <jdetiber@redhat.com> | 2015-11-23 11:33:41 -0500 | 
| commit | 3cbe7df8461e5514773e416d137980ce9bedf33d (patch) | |
| tree | b3dddcc0a4004fa09fae262c0a9385c7ed73796f | |
| parent | 8e979def0a56b40ab8a3acbd2e1a146457a5aaa6 (diff) | |
| download | openshift-3cbe7df8461e5514773e416d137980ce9bedf33d.tar.gz openshift-3cbe7df8461e5514773e416d137980ce9bedf33d.tar.bz2 openshift-3cbe7df8461e5514773e416d137980ce9bedf33d.tar.xz openshift-3cbe7df8461e5514773e416d137980ce9bedf33d.zip | |
Refactor master identity provider configuration
- Remote template in favor of a filter plugin
- Add additional validation for identity provider config
- Add mappingMethod attribute for identity providers, default to 'claim'
| -rw-r--r-- | filter_plugins/openshift_master.py | 469 | ||||
| -rw-r--r-- | roles/openshift_master/tasks/main.yml | 16 | ||||
| -rw-r--r-- | roles/openshift_master/templates/master.yaml.v1.j2 | 19 | ||||
| -rw-r--r-- | roles/openshift_master/templates/v1_partials/oauthConfig.j2 | 93 | 
4 files changed, 498 insertions, 99 deletions
| diff --git a/filter_plugins/openshift_master.py b/filter_plugins/openshift_master.py new file mode 100644 index 000000000..76fe610a0 --- /dev/null +++ b/filter_plugins/openshift_master.py @@ -0,0 +1,469 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +Custom filters for use in openshift-master +''' +import copy +import sys +import yaml + +from ansible import errors +from ansible.runner.filter_plugins.core import bool as ansible_bool + + +class IdentityProviderBase(object): +    """ IdentityProviderBase + +        Attributes: +            name (str): Identity provider Name +            login (bool): Is this identity provider a login provider? +            challenge (bool): Is this identity provider a challenge provider? +            provider (dict): Provider specific config +            _idp (dict): internal copy of the IDP dict passed in +            _required (list): List of lists of strings for required attributes +            _optional (list): List of lists of strings for optional attributes +            _allow_additional (bool): Does this provider support attributes +                not in _required and _optional + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    # disabling this check since the number of instance attributes are +    # necessary for this class +    # pylint: disable=too-many-instance-attributes +    def __init__(self, api_version, idp): +        if api_version not in ['v1']: +            raise errors.AnsibleFilterError("|failed api version {0} unknown".format(api_version)) + +        self._idp = copy.deepcopy(idp) + +        if 'name' not in self._idp: +            raise errors.AnsibleFilterError("|failed identity provider missing a name") + +        if 'kind' not in self._idp: +            raise errors.AnsibleFilterError("|failed identity provider missing a kind") + +        self.name = self._idp.pop('name') +        self.login = ansible_bool(self._idp.pop('login', False)) +        self.challenge = ansible_bool(self._idp.pop('challenge', False)) +        self.provider = dict(apiVersion=api_version, kind=self._idp.pop('kind')) + +        self._required = [['mappingMethod', 'mapping_method']] +        self._optional = [] +        self._allow_additional = True + +    @staticmethod +    def validate_idp_list(idp_list): +        ''' validates a list of idps ''' +        login_providers = [x.name for x in idp_list if x.login] +        if len(login_providers) > 1: +            raise errors.AnsibleFilterError("|failed multiple providers are " +                                            "not allowed for login. login " +                                            "providers: {0}".format(', '.join(login_providers))) + +        names = [x.name for x in idp_list] +        if len(set(names)) != len(names): +            raise errors.AnsibleFilterError("|failed more than one provider configured with the same name") + +        for idp in idp_list: +            idp.validate() + +    def validate(self): +        ''' validate an instance of this idp class ''' +        valid_mapping_methods = ['add', 'claim', 'generate', 'lookup'] +        if self.provider['mappingMethod'] not in valid_mapping_methods: +            raise errors.AnsibleFilterError("|failed unkown mapping method " +                                            "for provider {0}".format(self.__class__.__name__)) + +    @staticmethod +    def get_default(key): +        ''' get a default value for a given key ''' +        if key == 'mappingMethod': +            return 'claim' +        else: +            return None + +    def set_provider_item(self, items, required=False): +        ''' set a provider item based on the list of item names provided. ''' +        for item in items: +            provider_key = items[0] +            if item in self._idp: +                self.provider[provider_key] = self._idp.pop(item) +                break +        else: +            default = self.get_default(provider_key) +            if default is not None: +                self.provider[provider_key] = default +            elif required: +                raise errors.AnsibleFilterError("|failed provider {0} missing " +                                                "required key {1}".format(self.__class__.__name__, provider_key)) + +    def set_provider_items(self): +        ''' set the provider items for this idp ''' +        for items in self._required: +            self.set_provider_item(items, True) +        for items in self._optional: +            self.set_provider_item(items) +        if self._allow_additional: +            for key in self._idp.keys(): +                self.set_provider_item([key]) +        else: +            if len(self._idp) > 0: +                raise errors.AnsibleFilterError("|failed provider {0} " +                                                "contains unknown keys " +                                                "{1}".format(self.__class__.__name__, ', '.join(self._idp.keys()))) + +    def to_dict(self): +        ''' translate this idp to a dictionary ''' +        return dict(name=self.name, challenge=self.challenge, +                    login=self.login, provider=self.provider) + + +class LDAPPasswordIdentityProvider(IdentityProviderBase): +    """ LDAPPasswordIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False +        self._required += [['attributes'], ['url'], ['insecure']] +        self._optional += [['ca'], +                           ['bindDN', 'bind_dn'], +                           ['bindPassword', 'bind_password']] + +        self._idp['insecure'] = ansible_bool(self._idp.pop('insecure', False)) + +        if 'attributes' in self._idp and 'preferred_username' in self._idp['attributes']: +            pref_user = self._idp['attributes'].pop('preferred_username') +            self._idp['attributes']['preferredUsername'] = pref_user + +    def validate(self): +        ''' validate this idp instance ''' +        IdentityProviderBase.validate(self) +        if not isinstance(self.provider['attributes'], dict): +            raise errors.AnsibleFilterError("|failed attributes for provider " +                                            "{0} must be a dictionary".format(self.__class__.__name__)) + +        attrs = ['id', 'email', 'name', 'preferredUsername'] +        for attr in attrs: +            if attr in self.provider['attributes'] and not isinstance(self.provider['attributes'][attr], list): +                raise errors.AnsibleFilterError("|failed {0} attribute for " +                                                "provider {1} must be a list".format(attr, self.__class__.__name__)) + +        unknown_attrs = set(self.provider['attributes'].keys()) - set(attrs) +        if len(unknown_attrs) > 0: +            raise errors.AnsibleFilterError("|failed provider {0} has unknown " +                                            "attributes: {1}".format(self.__class__.__name__, ', '.join(unknown_attrs))) + + +class KeystonePasswordIdentityProvider(IdentityProviderBase): +    """ KeystoneIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False +        self._required += [['url'], ['domainName', 'domain_name']] +        self._optional += [['ca'], ['certFile', 'cert_file'], ['keyFile', 'key_file']] + + +class RequestHeaderIdentityProvider(IdentityProviderBase): +    """ RequestHeaderIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False +        self._required += [['headers']] +        self._optional += [['challengeURL', 'challenge_url'], +                           ['loginURL', 'login_url'], +                           ['clientCA', 'client_ca']] + +    def validate(self): +        ''' validate this idp instance ''' +        IdentityProviderBase.validate(self) +        if not isinstance(self.provider['headers'], list): +            raise errors.AnsibleFilterError("|failed headers for provider {0} " +                                            "must be a list".format(self.__class__.__name__)) + + +class AllowAllPasswordIdentityProvider(IdentityProviderBase): +    """ AllowAllPasswordIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False + + +class DenyAllPasswordIdentityProvider(IdentityProviderBase): +    """ DenyAllPasswordIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False + + +class HTPasswdPasswordIdentityProvider(IdentityProviderBase): +    """ HTPasswdPasswordIdentity + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False +        self._required += [['file', 'filename', 'fileName', 'file_name']] + +    @staticmethod +    def get_default(key): +        if key == 'file': +            return '/etc/origin/htpasswd' +        else: +            return IdentityProviderBase.get_default(key) + + +class BasicAuthPasswordIdentityProvider(IdentityProviderBase): +    """ BasicAuthPasswordIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False +        self._required += [['ca'], ['certFile', 'cert_file'], ['keyFile', 'key_file']] +        self._optional += [['key']] + + +class IdentityProviderOauthBase(IdentityProviderBase): +    """ IdentityProviderOauthBase + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderBase.__init__(self, api_version, idp) +        self._allow_additional = False +        self._required += [['clientID', 'client_id'], ['clientSecret', 'client_secret']] + +    def validate(self): +        ''' validate this idp instance ''' +        IdentityProviderBase.validate(self) +        if self.challenge: +            raise errors.AnsibleFilterError("|failed provider {0} does not " +                                            "allow challenge authentication".format(self.__class__.__name__)) + + +class OpenIDIdentityProvider(IdentityProviderOauthBase): +    """ OpenIDIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderOauthBase.__init__(self, api_version, idp) +        self._required += [['claims'], ['urls']] +        self._optional += [['ca'], +                           ['extraScopes'], +                           ['extraAuthorizeParameters']] +        if 'claims' in self._idp and 'preferred_username' in self._idp['claims']: +            pref_user = self._idp['claims'].pop('preferred_username') +            self._idp['claims']['preferredUsername'] = pref_user +        if 'urls' in self._idp and 'user_info' in self._idp['urls']: +            user_info = self._idp['urls'].pop('user_info') +            self._idp['urls']['userInfo'] = user_info +        if 'extra_scopes' in self._idp: +            self._idp['extraScopes'] = self._idp.pop('extra_scopes') +        if 'extra_authorize_parameters' in self._idp: +            self._idp['extraAuthorizeParameters'] = self._idp.pop('extra_authorize_parameters') + +        if 'extraAuthorizeParameters' in self._idp: +            if 'include_granted_scopes' in self._idp['extraAuthorizeParameters']: +                val = ansible_bool(self._idp['extraAuthorizeParameters'].pop('include_granted_scopes')) +                self._idp['extraAuthorizeParameters']['include_granted_scopes'] = val + + +    def validate(self): +        ''' validate this idp instance ''' +        IdentityProviderOauthBase.validate(self) +        if not isinstance(self.provider['claims'], dict): +            raise errors.AnsibleFilterError("|failed claims for provider {0} " +                                            "must be a dictionary".format(self.__class__.__name__)) + +        if 'extraScopes' not in self.provider['extraScopes'] and not isinstance(self.provider['extraScopes'], list): +            raise errors.AnsibleFilterError("|failed extraScopes for provider " +                                            "{0} must be a list".format(self.__class__.__name__)) +        if ('extraAuthorizeParameters' not in self.provider['extraAuthorizeParameters'] +                and not  isinstance(self.provider['extraAuthorizeParameters'], dict)): +            raise errors.AnsibleFilterError("|failed extraAuthorizeParameters " +                                            "for provider {0} must be a dictionary".format(self.__class__.__name__)) + +        required_claims = ['id'] +        optional_claims = ['email', 'name', 'preferredUsername'] +        all_claims = required_claims + optional_claims + +        for claim in required_claims: +            if claim in required_claims and claim not in self.provider['claims']: +                raise errors.AnsibleFilterError("|failed {0} claim missing " +                                                "for provider {1}".format(claim, self.__class__.__name__)) + +        for claim in all_claims: +            if claim in self.provider['claims'] and not isinstance(self.provider['claims'][claim], list): +                raise errors.AnsibleFilterError("|failed {0} claims for " +                                                "provider {1} must be a list".format(claim, self.__class__.__name__)) + +        unknown_claims = set(self.provider['claims'].keys()) - set(all_claims) +        if len(unknown_claims) > 0: +            raise errors.AnsibleFilterError("|failed provider {0} has unknown " +                                            "claims: {1}".format(self.__class__.__name__, ', '.join(unknown_claims))) + +        if not isinstance(self.provider['urls'], dict): +            raise errors.AnsibleFilterError("|failed urls for provider {0} " +                                            "must be a dictionary".format(self.__class__.__name__)) + +        required_urls = ['authorize', 'token'] +        optional_urls = ['userInfo'] +        all_urls = required_urls + optional_urls + +        for url in required_urls: +            if url not in self.provider['urls']: +                raise errors.AnsibleFilterError("|failed {0} url missing for " +                                                "provider {1}".format(url, self.__class__.__name__)) + +        unknown_urls = set(self.provider['urls'].keys()) - set(all_urls) +        if len(unknown_urls) > 0: +            raise errors.AnsibleFilterError("|failed provider {0} has unknown " +                                            "urls: {1}".format(self.__class__.__name__, ', '.join(unknown_urls))) + + +class GoogleIdentityProvider(IdentityProviderOauthBase): +    """ GoogleIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    def __init__(self, api_version, idp): +        IdentityProviderOauthBase.__init__(self, api_version, idp) +        self._optional += [['hostedDomain', 'hosted_domain']] + + +class GitHubIdentityProvider(IdentityProviderOauthBase): +    """ GitHubIdentityProvider + +        Attributes: + +        Args: +            api_version(str): OpenShift config version +            idp (dict): idp config dict + +        Raises: +            AnsibleFilterError: +    """ +    pass + + +class FilterModule(object): +    ''' Custom ansible filters for use by the openshift_master role''' + +    @staticmethod +    def translate_idps(idps, api_version): +        ''' Translates a list of dictionaries into a valid identityProviders config ''' +        idp_list = [] + +        if not isinstance(idps, list): +            raise errors.AnsibleFilterError("|failed expects to filter on a list of identity providers") +        for idp in idps: +            if not isinstance(idp, dict): +                raise errors.AnsibleFilterError("|failed identity providers must be a list of dictionaries") + +            cur_module = sys.modules[__name__] +            idp_class = getattr(cur_module, idp['kind'], None) +            idp_inst = idp_class(api_version, idp) if idp_class is not None else IdentityProviderBase(api_version, idp) +            idp_inst.set_provider_items() +            idp_list.append(idp_inst) + + +        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} diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml index 185bfb8f3..ed174dbfc 100644 --- a/roles/openshift_master/tasks/main.yml +++ b/roles/openshift_master/tasks/main.yml @@ -1,13 +1,16 @@  --- -# TODO: add validation for openshift_master_identity_providers  # TODO: add ability to configure certificates given either a local file to  #       point to or certificate contents, set in default cert locations. -- assert: -    that: -    - openshift_master_oauth_grant_method in openshift_master_valid_grant_methods -  when: openshift_master_oauth_grant_method is defined +# Authentication Variable Validation +# TODO: validate the different identity provider kinds as well +- fail: +    msg: > +      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"    when: openshift_master_ha | bool and ((openshift_master_cluster_method is not defined) or (openshift_master_cluster_method is defined and openshift_master_cluster_method not in ["native", "pacemaker"])) @@ -172,6 +175,9 @@    - restart master    - restart master api +- set_fact: +    translated_identity_providers: "{{ openshift_master_identity_providers | translate_idps('v1') }}" +  # TODO: add the validate parameter when there is a validation command to run  - name: Create master config    template: diff --git a/roles/openshift_master/templates/master.yaml.v1.j2 b/roles/openshift_master/templates/master.yaml.v1.j2 index 2a37c06d9..9f4a17f0a 100644 --- a/roles/openshift_master/templates/master.yaml.v1.j2 +++ b/roles/openshift_master/templates/master.yaml.v1.j2 @@ -107,7 +107,24 @@ networkConfig:  {% endif %}  # serviceNetworkCIDR must match kubernetesMasterConfig.servicesSubnet    serviceNetworkCIDR: {{ openshift.master.portal_net }} -{% include 'v1_partials/oauthConfig.j2' %} +oauthConfig: +  assetPublicURL: {{ openshift.master.public_console_url }}/ +  grantConfig: +    method: {{ openshift.master.oauth_grant_method }} +  identityProviders: +{% for line in translated_identity_providers.splitlines() %} +  {{ line }} +{% endfor %} +  masterCA: ca.crt +  masterPublicURL: {{ openshift.master.public_api_url }} +  masterURL: {{ openshift.master.api_url }} +  sessionConfig: +    sessionMaxAgeSeconds: {{ openshift.master.session_max_seconds }} +    sessionName: {{ openshift.master.session_name }} +    sessionSecretsFile: {{ openshift.master.session_secrets_file }} +  tokenConfig: +    accessTokenMaxAgeSeconds: {{ openshift.master.access_token_max_seconds }} +    authorizeTokenMaxAgeSeconds: {{ openshift.master.auth_token_max_seconds }}  pauseControllers: false  policyConfig:    bootstrapPolicyFile: {{ openshift_master_policy }} diff --git a/roles/openshift_master/templates/v1_partials/oauthConfig.j2 b/roles/openshift_master/templates/v1_partials/oauthConfig.j2 deleted file mode 100644 index 8a4f5a746..000000000 --- a/roles/openshift_master/templates/v1_partials/oauthConfig.j2 +++ /dev/null @@ -1,93 +0,0 @@ -{% macro identity_provider_config(identity_provider) %} -      apiVersion: v1 -      kind: {{ identity_provider.kind }} -{% if identity_provider.kind == 'HTPasswdPasswordIdentityProvider' %} -      file: {{ identity_provider.filename }} -{% elif identity_provider.kind == 'BasicAuthPasswordIdentityProvider' %} -      url: {{ identity_provider.url }} -{% for key in ('ca', 'certFile', 'keyFile') %} -{% if key in identity_provider %} -      {{ key }}: "{{ identity_provider[key] }}" -{% endif %} -{% endfor %} -{% elif identity_provider.kind == 'LDAPPasswordIdentityProvider' %} -      attributes: -{% for attribute_key in identity_provider.attributes %} -        {{ attribute_key }}: -{% for attribute_value in identity_provider.attributes[attribute_key] %} -        - {{ attribute_value }} -{% endfor %} -{% endfor %} -{% for key in ('bindDN', 'bindPassword', 'ca') %} -      {{ key }}: "{{ identity_provider[key] }}" -{% endfor %} -{% for key in ('insecure', 'url') %} -      {{ key }}: {{ identity_provider[key] }} -{% endfor %} -{% elif identity_provider.kind == 'RequestHeaderIdentityProvider' %} -      headers: {{ identity_provider.headers }} -{% if 'clientCA' in identity_provider %} -      clientCA: {{ identity_provider.clientCA }} -{% endif %} -{% elif identity_provider.kind == 'GitHubIdentityProvider' %} -      clientID: {{ identity_provider.clientID }} -      clientSecret: {{ identity_provider.clientSecret }} -{% elif identity_provider.kind == 'GoogleIdentityProvider' %} -      clientID: {{ identity_provider.clientID }} -      clientSecret: {{ identity_provider.clientSecret }} -{% if 'hostedDomain' in identity_provider %} -      hostedDomain: {{ identity_provider.hostedDomain }} -{% endif %} -{% elif identity_provider.kind == 'OpenIDIdentityProvider' %} -      clientID: {{ identity_provider.clientID }} -      clientSecret: {{ identity_provider.clientSecret }} -      claims: -        id: identity_provider.claims.id -{% for claim_key in ('preferredUsername', 'name', 'email') %} -{% if claim_key in identity_provider.claims %} -        {{ claim_key }}: {{ identity_provider.claims[claim_key] }} -{% endif %} -{% endfor %} -      urls: -        authorize: {{ identity_provider.urls.authorize }} -        token: {{ identity_provider.urls.token }} -{% if 'userInfo' in identity_provider.urls %} -        userInfo: {{ identity_provider.userInfo }} -{% endif %} -{% if 'extraScopes' in identity_provider %} -      extraScopes: -{% for scope in identity_provider.extraScopes %} -      - {{ scope }} -{% endfor %} -{% endif %} -{% if 'extraAuthorizeParameters' in identity_provider %} -      extraAuthorizeParameters: -{% for param_key, param_value in identity_provider.extraAuthorizeParameters.iteritems() %} -        {{ param_key }}: {{ param_value }} -{% endfor %} -{% endif %} -{% endif %} -{% endmacro %} -oauthConfig: -  assetPublicURL: {{ openshift.master.public_console_url }}/ -  grantConfig: -    method: {{ openshift.master.oauth_grant_method }} -  identityProviders: -{% for identity_provider in openshift.master.identity_providers %} -  - name: {{ identity_provider.name }} -    challenge: {{ identity_provider.challenge }} -    login: {{ identity_provider.login }} -    provider: -{{ identity_provider_config(identity_provider) }} -{%- endfor %} -  masterCA: ca.crt -  masterPublicURL: {{ openshift.master.public_api_url }} -  masterURL: {{ openshift.master.api_url }} -  sessionConfig: -    sessionMaxAgeSeconds: {{ openshift.master.session_max_seconds }} -    sessionName: {{ openshift.master.session_name }} -    sessionSecretsFile: {{ openshift.master.session_secrets_file }} -  tokenConfig: -    accessTokenMaxAgeSeconds: {{ openshift.master.access_token_max_seconds }} -    authorizeTokenMaxAgeSeconds: {{ openshift.master.auth_token_max_seconds }} -{# Comment to preserve newline after authorizeTokenMaxAgeSeconds #} | 
