Add support for connecting via https with a client certificate

This commit is contained in:
Hiroaki Nakamura 2016-06-30 01:01:07 +09:00
parent 58b94aecac
commit 61020a87dd

View file

@ -128,6 +128,34 @@ options:
- The unix domain socket path for the LXD server. - The unix domain socket path for the LXD server.
required: false required: false
default: /var/lib/lxd/unix.socket default: /var/lib/lxd/unix.socket
url:
description:
- The https URL for the LXD server.
- If url is set, this module connects to the LXD server via https.
If url it not set, this module connects to the LXD server via
unix domain socket specified with unix_socket_path.
key_file:
description:
- The client certificate key file path.
required: false
default: >
'{}/.config/lxc/client.key'.format(os.environ['HOME'])
cert_file:
description:
- The client certificate file path.
required: false
default: >
'{}/.config/lxc/client.crt'.format(os.environ['HOME'])
trust_password:
description:
- The client trusted password.
- You need to set this password on the LXD server before
running this module using the following command.
lxc config set core.trust_password <some random password>
See https://www.stgraber.org/2016/04/18/lxd-api-direct-interaction/
- If trust_password is set, this module send a request for
authentication before sending any requests.
required: false
notes: notes:
- Containers must have a unique name. If you attempt to create a container - Containers must have a unique name. If you attempt to create a container
with a name that already existed in the users namespace the module will with a name that already existed in the users namespace the module will
@ -205,11 +233,17 @@ EXAMPLES = """
alias: "ubuntu/xenial/amd64" alias: "ubuntu/xenial/amd64"
profiles: ["default"] profiles: ["default"]
# An example for connecting to the LXD server using https
- hosts: localhost - hosts: localhost
connection: local connection: local
tasks: tasks:
- name: create macvlan profile - name: create macvlan profile
lxd_container: lxd_container:
url: https://127.0.0.1:8443
# These cert_file and key_file values are equal to the default values.
#cert_file: "{{ lookup('env', 'HOME') }}/.config/lxc/client.crt"
#key_file: "{{ lookup('env', 'HOME') }}/.config/lxc/client.key"
trust_password: mypassword
type: profile type: profile
name: macvlan name: macvlan
state: present state: present
@ -247,6 +281,8 @@ lxd_container:
sample: ["create", "start"] sample: ["create", "start"]
""" """
import os
try: try:
import json import json
except ImportError: except ImportError:
@ -254,11 +290,12 @@ except ImportError:
# httplib/http.client connection using unix domain socket # httplib/http.client connection using unix domain socket
import socket import socket
import ssl
try: try:
from httplib import HTTPConnection from httplib import HTTPConnection, HTTPSConnection
except ImportError: except ImportError:
# Python 3 # Python 3
from http.client import HTTPConnection from http.client import HTTPConnection, HTTPSConnection
class UnixHTTPConnection(HTTPConnection): class UnixHTTPConnection(HTTPConnection):
def __init__(self, path, timeout=None): def __init__(self, path, timeout=None):
@ -270,6 +307,12 @@ class UnixHTTPConnection(HTTPConnection):
sock.connect(self.path) sock.connect(self.path)
self.sock = sock self.sock = sock
from ansible.module_utils.urls import generic_urlparse
try:
from urlparse import urlparse
except ImportError:
# Python 3
from url.parse import urlparse
# LXD_ANSIBLE_STATES is a map of states that contain values of methods used # LXD_ANSIBLE_STATES is a map of states that contain values of methods used
# when a particular state is evoked. # when a particular state is evoked.
@ -326,7 +369,17 @@ class LxdContainerManagement(object):
self.force_stop = self.module.params['force_stop'] self.force_stop = self.module.params['force_stop']
self.addresses = None self.addresses = None
self.unix_socket_path = self.module.params['unix_socket_path'] self.unix_socket_path = self.module.params['unix_socket_path']
self.url = self.module.params.get('url', None)
self.key_file = self.module.params.get('key_file', None)
self.cert_file = self.module.params.get('cert_file', None)
self.trust_password = self.module.params.get('trust_password', None)
if self.url is None:
self.connection = UnixHTTPConnection(self.unix_socket_path, timeout=self.timeout) self.connection = UnixHTTPConnection(self.unix_socket_path, timeout=self.timeout)
else:
parts = generic_urlparse(urlparse(self.url))
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.load_cert_chain(self.cert_file, keyfile=self.key_file)
self.connection = HTTPSConnection(parts.get('netloc'), context=ctx, timeout=self.timeout)
self.logs = [] self.logs = []
self.actions = [] self.actions = []
@ -337,6 +390,10 @@ class LxdContainerManagement(object):
if param_val is not None: if param_val is not None:
self.config[attr] = param_val self.config[attr] = param_val
def _authenticate(self):
body_json = {'type': 'client', 'password': self.trust_password}
self._send_request('POST', '/1.0/certificates', body_json=body_json)
def _send_request(self, method, url, body_json=None, ok_error_codes=None): def _send_request(self, method, url, body_json=None, ok_error_codes=None):
try: try:
body = json.dumps(body_json) body = json.dumps(body_json)
@ -359,8 +416,18 @@ class LxdContainerManagement(object):
logs=self.logs logs=self.logs
) )
return resp_json return resp_json
except socket.error: except socket.error as e:
self.module.fail_json(msg='cannot connect to the LXD server', unix_socket_path=self.unix_socket_path) if self.url is None:
self.module.fail_json(
msg='cannot connect to the LXD server',
unix_socket_path=self.unix_socket_path, error=e
)
else:
self.module.fail_json(
msg='cannot connect to the LXD server',
url=self.url, key_file=self.key_file, cert_file=self.cert_file,
error=e
)
def _operate_and_wait(self, method, path, body_json=None): def _operate_and_wait(self, method, path, body_json=None):
resp_json = self._send_request(method, path, body_json=body_json) resp_json = self._send_request(method, path, body_json=body_json)
@ -636,6 +703,9 @@ class LxdContainerManagement(object):
def run(self): def run(self):
"""Run the main method.""" """Run the main method."""
if self.trust_password is not None:
self._authenticate()
if self.type == 'container': if self.type == 'container':
self.old_container_json = self._get_container_json() self.old_container_json = self._get_container_json()
self.old_state = self._container_json_to_module_state(self.old_container_json) self.old_state = self._container_json_to_module_state(self.old_container_json)
@ -715,6 +785,20 @@ def main():
unix_socket_path=dict( unix_socket_path=dict(
type='str', type='str',
default='/var/lib/lxd/unix.socket' default='/var/lib/lxd/unix.socket'
),
url=dict(
type='str',
),
key_file=dict(
type='str',
default='{}/.config/lxc/client.key'.format(os.environ['HOME'])
),
cert_file=dict(
type='str',
default='{}/.config/lxc/client.crt'.format(os.environ['HOME'])
),
trust_password=dict(
type='str',
) )
), ),
supports_check_mode=False, supports_check_mode=False,