summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2012-01-17 16:38:01 -0500
committerScott Moser <smoser@ubuntu.com>2012-01-17 16:38:01 -0500
commit530d8f9bda663d12f6bba2f20b325bc3c4220a9e (patch)
tree70f0e09e648edb3392229765a234597a9857f77f /cloudinit
parent1e746f00edbf478cf0ae43b66ff7899b6819fa33 (diff)
parentce05d60cbe7a542c51e2fa206acf57e59091f17a (diff)
downloadvyos-cloud-init-530d8f9bda663d12f6bba2f20b325bc3c4220a9e.tar.gz
vyos-cloud-init-530d8f9bda663d12f6bba2f20b325bc3c4220a9e.zip
add support for add/remove CA Certificates via cloud-config (LP: #915232)
LP: #915232
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/CloudConfig/cc_ca_certs.py88
-rw-r--r--cloudinit/util.py47
2 files changed, 130 insertions, 5 deletions
diff --git a/cloudinit/CloudConfig/cc_ca_certs.py b/cloudinit/CloudConfig/cc_ca_certs.py
new file mode 100644
index 00000000..c18821f9
--- /dev/null
+++ b/cloudinit/CloudConfig/cc_ca_certs.py
@@ -0,0 +1,88 @@
+# vi: ts=4 expandtab
+#
+# Author: Mike Milner <mike.milner@canonical.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3, as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+from subprocess import check_call
+from cloudinit.util import (write_file, get_cfg_option_list_or_str,
+ delete_dir_contents)
+
+CA_CERT_PATH = "/usr/share/ca-certificates/"
+CA_CERT_FILENAME = "cloud-init-ca-certs.crt"
+CA_CERT_CONFIG = "/etc/ca-certificates.conf"
+CA_CERT_SYSTEM_PATH = "/etc/ssl/certs/"
+
+
+def update_ca_certs():
+ """
+ Updates the CA certificate cache on the current machine.
+ """
+ check_call(["update-ca-certificates"])
+
+
+def add_ca_certs(certs):
+ """
+ Adds certificates to the system. To actually apply the new certificates
+ you must also call L{update_ca_certs}.
+
+ @param certs: A list of certificate strings.
+ """
+ if certs:
+ cert_file_contents = "\n".join(certs)
+ cert_file_fullpath = os.path.join(CA_CERT_PATH, CA_CERT_FILENAME)
+ write_file(cert_file_fullpath, cert_file_contents, mode=0644)
+ # Append cert filename to CA_CERT_CONFIG file.
+ write_file(CA_CERT_CONFIG, "\n%s" % CA_CERT_FILENAME, omode="a")
+
+
+def remove_default_ca_certs():
+ """
+ Removes all default trusted CA certificates from the system. To actually
+ apply the change you must also call L{update_ca_certs}.
+ """
+ delete_dir_contents(CA_CERT_PATH)
+ delete_dir_contents(CA_CERT_SYSTEM_PATH)
+ write_file(CA_CERT_CONFIG, "", mode=0644)
+
+
+def handle(_name, cfg, _cloud, log, _args):
+ """
+ Call to handle ca-cert sections in cloud-config file.
+
+ @param name: The module name "ca-cert" from cloud.cfg
+ @param cfg: A nested dict containing the entire cloud config contents.
+ @param cloud: The L{CloudInit} object in use.
+ @param log: Pre-initialized Python logger object to use for logging.
+ @param args: Any module arguments from cloud.cfg
+ """
+ # If there isn't a ca-certs section in the configuration don't do anything
+ if "ca-certs" not in cfg:
+ return
+ ca_cert_cfg = cfg['ca-certs']
+
+ # If there is a remove-defaults option set to true, remove the system
+ # default trusted CA certs first.
+ if ca_cert_cfg.get("remove-defaults", False):
+ log.debug("removing default certificates")
+ remove_default_ca_certs()
+
+ # If we are given any new trusted CA certs to add, add them.
+ if "trusted" in ca_cert_cfg:
+ trusted_certs = get_cfg_option_list_or_str(ca_cert_cfg, "trusted")
+ if trusted_certs:
+ log.debug("adding %d certificates" % len(trusted_certs))
+ add_ca_certs(trusted_certs)
+
+ # Update the system with the new cert configuration.
+ update_ca_certs()
diff --git a/cloudinit/util.py b/cloudinit/util.py
index d8d735cc..e4337e3a 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -18,6 +18,7 @@
import yaml
import os
import os.path
+import shutil
import errno
import subprocess
from Cheetah.Template import Template
@@ -94,13 +95,24 @@ def get_cfg_option_str(yobj, key, default=None):
def get_cfg_option_list_or_str(yobj, key, default=None):
- if key not in yobj:
+ """
+ Gets the C{key} config option from C{yobj} as a list of strings. If the
+ key is present as a single string it will be returned as a list with one
+ string arg.
+
+ @param yobj: The configuration object.
+ @param key: The configuration key to get.
+ @param default: The default to return if key is not found.
+ @return: The configuration option as a list of strings or default if key
+ is not found.
+ """
+ if not key in yobj:
return default
if yobj[key] is None:
return []
if isinstance(yobj[key], list):
return yobj[key]
- return([yobj[key]])
+ return [yobj[key]]
# get a cfg entry by its path array
@@ -114,9 +126,11 @@ def get_cfg_by_path(yobj, keyp, default=None):
return(cur)
-# merge values from cand into source
-# if src has a key, cand will not override
def mergedict(src, cand):
+ """
+ Merge values from C{cand} into C{src}. If C{src} has a key C{cand} will
+ not override. Nested dictionaries are merged recursively.
+ """
if isinstance(src, dict) and isinstance(cand, dict):
for k, v in cand.iteritems():
if k not in src:
@@ -126,7 +140,30 @@ def mergedict(src, cand):
return src
+def delete_dir_contents(dirname):
+ """
+ Deletes all contents of a directory without deleting the directory itself.
+
+ @param dirname: The directory whose contents should be deleted.
+ """
+ for node in os.listdir(dirname):
+ node_fullpath = os.path.join(dirname, node)
+ if os.path.isdir(node_fullpath):
+ shutil.rmtree(node_fullpath)
+ else:
+ os.unlink(node_fullpath)
+
+
def write_file(filename, content, mode=0644, omode="wb"):
+ """
+ Writes a file with the given content and sets the file mode as specified.
+ Resotres the SELinux context if possible.
+
+ @param filename: The full path of the file to write.
+ @param content: The content to write to the file.
+ @param mode: The filesystem mode to set on the file.
+ @param omode: The open mode used when opening the file (r, rb, a, etc.)
+ """
try:
os.makedirs(os.path.dirname(filename))
except OSError as e:
@@ -134,7 +171,7 @@ def write_file(filename, content, mode=0644, omode="wb"):
raise e
f = open(filename, omode)
- if mode != None:
+ if mode is not None:
os.chmod(filename, mode)
f.write(content)
f.close()