Repository: openshift/openshift-ansible

Issue:
Allow users to add the CA for their named certificates to the generated CA
@abutcher FYI

In situations where someone has provided a named cert that's signed for the same name as the internal API hostname we should allow them to provide the CA for that cert. This would allow us to append it to our generated CA before we distribute it to nodes and create the secret for the service account. This means that nodes and pods referencing the master API will trust the cert even if the cert presented is the named cert.

This is all @liggitt's idea, he may be able to add some color commentary if I've not conveyed everything correctly.



Discussion:
Once https://github.com/openshift/origin/pull/7839 merges, you'll have the following tools at your disposal:
- `oadm ca create-master-certs` will accept additional custom CA files via `--certificate-authority` args, include those extra CAs in all generated kubeconfigs, and will output a `ca-bundle.crt` file containing the generated CA and any provided CA files (which can be referenced instead of `ca.crt` for `masterCA` in the master config)
- `oadm create-api-client-config` and `oadm create-node-config` will also accept multiple `--certificate-authority` args

I would expect ansible to:
- For 3.2+, take an optional list of custom CA files, and feed those to the `create-master-certs` and `create-api-client-config` calls
- If a `ca-bundle.crt` file exists (which it will after calling `create-master-certs` with 3.2+), use it as the masterCA file in the master config, otherwise, use the ca.crt file
- Where we currently warn if a "named certificate" overrides the internal master URL, mention they should provide the CA for that cert (and maybe suppress the warning if they have)

Hi guys,
we also need this for several use cases.

As a workaround, we performed the following steps:
- Install from scratch and use generated CA
- Append our own CA to the master/ca.crt file
- Regenerate all kubeconfig files with  oadm create-kubeconfig --certificate-authority=$OSE_CA_BUNDLE with OSE_CA_BUNDLE pointing to your new ca.crt

just FYI since this was necessary for a deployment I was making and this seem to be taking quite some time to merge, I have added this to my ansible config : 

```
--- /usr/share/ansible/openshift-ansible/roles/openshift_master_ca/tasks/main.yml~      2016-06-07 12:17:15.861832656 +0800
+++ /usr/share/ansible/openshift-ansible/roles/openshift_master_ca/tasks/main.yml       2016-06-07 13:50:20.840783784 +0800
@@ -21,3 +21,18 @@
       --public-master={{ openshift.master.public_api_url }}
       --cert-dir={{ openshift_master_config_dir }} --overwrite=false
   when: master_certs_missing | bool
+
+- name: Add custom CA to the ca.crt
+  shell: >
+     [[ $(grep -c BEGIN\ CERTIFICATE /etc/origin/master/ca.crt) > 1 ]]  || \
+       cat {{ openshift_master_custom_ca }} >> {{ openshift.common.config_base }}/master/ca.crt
+  when: openshift_master_custom_ca is defined
+
+- name: Regenerate the master certificates if they do not already exist
+  command: >
+    {{ openshift.common.admin_binary }} create-master-certs
+      --hostnames={{ master_hostnames | join(',') }}
+      --master={{ openshift.master.api_url }}
+      --public-master={{ openshift.master.public_api_url }}
+      --cert-dir={{ openshift_master_config_dir }} --overwrite=false
+  when: openshift_master_custom_ca is defined
```

I just need to add a `openshift_master_custom_ca` variable in my ansible host file pointing to  my custom CA and this would just work.

Now I understand that I am a total ansible noob so it's probably sore for the eyes of experts and not sending this for a PR  just yet. What do you think @liggitt @sdodson is it worthwhile to propose something like this now until the proper way by the code get merged?  

Hi @chmouel what about propsing a PR ?

@akram yeah planning to :) just making sure with the others in case there is another solution being worked on planned to be merged sometime soon, 

@abutcher can you coordinate getting this lined up with the certificate changes you have in the pipe?

@detiber Yeah, I'll add this to https://github.com/openshift/openshift-ansible/pull/1990. Thanks @chmouel !


Resolving patch:
diff --git a/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py
--- a/filter_plugins/oo_filters.py
+++ b/filter_plugins/oo_filters.py
@@ -37,6 +37,9 @@ def oo_pdb(arg):
     def get_attr(data, attribute=None):
         """ This looks up dictionary attributes of the form a.b.c and returns
             the value.
+
+            If the key isn't present, None is returned.
+
             Ex: data = {'a': {'b': {'c': 5}}}
                 attribute = "a.b.c"
                 returns 5
@@ -46,7 +49,11 @@ def get_attr(data, attribute=None):
 
         ptr = data
         for attr in attribute.split('.'):
-            ptr = ptr[attr]
+            if attr in ptr:
+                ptr = ptr[attr]
+            else:
+                ptr = None
+                break
 
         return ptr
 
@@ -138,6 +145,7 @@ def oo_collect(data, attribute=None, filters=None):
         else:
             retval = [FilterModule.get_attr(d, attribute) for d in data]
 
+        retval = [val for val in retval if val != None]
         return retval
 
     @staticmethod
@@ -474,16 +482,20 @@ def oo_parse_named_certificates(certificates, named_certs_dir, internal_hostname
         """ Parses names from list of certificate hashes.
 
             Ex: certificates = [{ "certfile": "/root/custom1.crt",
-                                  "keyfile": "/root/custom1.key" },
+                                  "keyfile": "/root/custom1.key",
+                                   "cafile": "/root/custom-ca1.crt" },
                                 { "certfile": "custom2.crt",
-                                  "keyfile": "custom2.key" }]
+                                  "keyfile": "custom2.key",
+                                  "cafile": "custom-ca2.crt" }]
 
                 returns [{ "certfile": "/etc/origin/master/named_certificates/custom1.crt",
                            "keyfile": "/etc/origin/master/named_certificates/custom1.key",
+                           "cafile": "/etc/origin/master/named_certificates/custom-ca1.crt",
                            "names": [ "public-master-host.com",
                                       "other-master-host.com" ] },
                          { "certfile": "/etc/origin/master/named_certificates/custom2.crt",
                            "keyfile": "/etc/origin/master/named_certificates/custom2.key",
+                           "cafile": "/etc/origin/master/named_certificates/custom-ca-2.crt",
                            "names": [ "some-hostname.com" ] }]
         """
         if not isinstance(named_certs_dir, basestring):
@@ -514,17 +526,20 @@ def oo_parse_named_certificates(certificates, named_certs_dir, internal_hostname
                 raise errors.AnsibleFilterError(("|failed to parse certificate '%s', " % certificate['certfile'] +
                                                  "please specify certificate names in host inventory"))
 
-            certificate['names'] = [name for name in certificate['names'] if name not in internal_hostnames]
-            certificate['names'] = list(set(certificate['names']))
-            if not certificate['names']:
-                raise errors.AnsibleFilterError(("|failed to parse certificate '%s' or " % certificate['certfile'] +
-                                                 "detected a collision with internal hostname, please specify " +
-                                                 "certificate names in host inventory"))
+            if 'cafile' not in certificate:
+                certificate['names'] = [name for name in certificate['names'] if name not in internal_hostnames]
+                certificate['names'] = list(set(certificate['names']))
+                if not certificate['names']:
+                    raise errors.AnsibleFilterError(("|failed to parse certificate '%s' or " % certificate['certfile'] +
+                                                     "detected a collision with internal hostname, please specify " +
+                                                     "certificate names in host inventory"))
 
         for certificate in certificates:
             # Update paths for configuration
             certificate['certfile'] = os.path.join(named_certs_dir, os.path.basename(certificate['certfile']))
             certificate['keyfile'] = os.path.join(named_certs_dir, os.path.basename(certificate['keyfile']))
+            if 'cafile' in certificate:
+                certificate['cafile'] = os.path.join(named_certs_dir, os.path.basename(certificate['cafile']))
         return certificates
 
     @staticmethod


Test patch: