diff --git a/doc/source/tls.rst b/doc/source/tls.rst
new file mode 100644
index 0000000000..446daaf2f5
--- /dev/null
+++ b/doc/source/tls.rst
@@ -0,0 +1,106 @@
+..
+ Copyright 2015 Rackspace
+ All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may
+ not use this file except in compliance with the License. You may obtain
+ a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations
+ under the License.
+
+========================
+Transport Layer Security
+========================
+
+Magnum uses TLS to secure communication between a Bay's services and the
+outside world. This includes not only Magnum itself, but also the end-user
+when they choose to use native client libraries to interact with the Bay.
+Magnum also uses TLS certificates for client authentication, which means each
+client needs a valid certificate to communicate with a Bay.
+
+TLS is a complex subject, and many guides on it exist already. This guide will
+not attempt to fully describe TLS, only the necessary pieces to get a client
+set up to talk to a Bay with TLS. A more indepth guide on TLS can be found in
+the `OpenSSL Cookbook `_
+by Ivan Ristić.
+
+
+Generating a Client Key and Certificate Signing Request
+=======================================================
+
+The first step to setting up a client is to generate your personal private key.
+This is essentially a cryptographically generated string of bytes. It should be
+protected as a password. To generate an RSA key, you will use the 'genrsa'
+command of the 'openssl' tool.
+
+::
+
+ openssl genrsa -out client.key 4096
+
+This command generates a 4096 byte RSA key at client.key.
+
+Next, you will need to generate a certificate signing request (CSR). This will
+be used by Magnum to generate a signed certificate you will use to communicate
+with the Bay. It is used by the Bay to secure the connection and validate you
+are you who say you are.
+
+To generate a CSR for client authentication, openssl requires a config file
+that specifies a few values. Below is a simple template, just fill in the 'CN'
+value with your name and save it as client.conf
+
+::
+
+ [req]
+ distinguished_name = req_distinguished_name
+ req_extensions = req_ext
+ x509_extensions = req_ext
+ prompt = no
+ [req_distinguished_name]
+ CN = Your Name
+ [req_ext]
+ extendedKeyUsage = clientAuth
+
+Once you have client.conf, you can run the openssl 'req' command to generate
+the CSR.
+
+::
+
+ openssl req -new -days 365
+ -config client.conf
+ -reqexts req_ext
+ -extensions req_ext
+ -key client.key
+ -out client.csr
+
+
+Now that you have your client CSR, you can use the Magnum CLI to send it off
+to Magnum to get it signed.
+
+::
+
+ magnum ca-sign --bay --csr client.csr > client.crt
+
+The final piece you need to retrieve is the CA certificate for the bay. This
+is used by your native client to ensure you're only communicating with hosts
+that Magnum set up.
+
+::
+
+ magnum ca-show --bay > ca.crt
+
+Once you have all of these pieces, you can configure your native client. Below
+is an example for Docker.
+
+::
+
+ docker -H tcp://:2376 --tls --tlsverify \
+ --tlscacert ca.crt \
+ --tlskey client.key \
+ --tlscert client.crt
+ info
diff --git a/magnum/api/controllers/v1/baymodel.py b/magnum/api/controllers/v1/baymodel.py
index 01b3f50881..53ed22dd31 100644
--- a/magnum/api/controllers/v1/baymodel.py
+++ b/magnum/api/controllers/v1/baymodel.py
@@ -122,6 +122,9 @@ class BayModel(base.APIBase):
labels = wtypes.DictType(str, str)
"""One or more key/value pairs"""
+ insecure = wsme.wsattr(types.boolean, default=False)
+ """Indicates whether the TLS should be disabled"""
+
def __init__(self, **kwargs):
self.fields = []
for field in objects.BayModel.fields:
diff --git a/magnum/conductor/handlers/bay_conductor.py b/magnum/conductor/handlers/bay_conductor.py
index 01578b5b06..121f95b144 100644
--- a/magnum/conductor/handlers/bay_conductor.py
+++ b/magnum/conductor/handlers/bay_conductor.py
@@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
from heatclient.common import template_utils
from heatclient import exc
@@ -124,6 +125,7 @@ class Handler(object):
try:
# Generate certificate and set the cert reference to bay
cert_manager.generate_certificates_to_bay(bay)
+ bay.uuid = uuid.uuid4()
created_stack = _create_stack(context, osc, bay,
bay_create_timeout)
except exc.HTTPBadRequest as e:
diff --git a/magnum/conductor/template_definition.py b/magnum/conductor/template_definition.py
index 3bb71de65b..2ee0cb0dc6 100644
--- a/magnum/conductor/template_definition.py
+++ b/magnum/conductor/template_definition.py
@@ -20,6 +20,7 @@ from pkg_resources import iter_entry_points
import requests
import six
+from magnum.common import clients
from magnum.common import exception
from magnum.common import paths
from magnum.i18n import _
@@ -107,16 +108,16 @@ class ParameterMapping(object):
value = None
if (self.baymodel_attr and
- getattr(baymodel, self.baymodel_attr, None)):
+ getattr(baymodel, self.baymodel_attr, None) is not None):
value = getattr(baymodel, self.baymodel_attr)
elif (self.bay_attr and
- getattr(bay, self.bay_attr, None)):
+ getattr(bay, self.bay_attr, None) is not None):
value = getattr(bay, self.bay_attr)
elif self.required:
kwargs = dict(heat_param=self.heat_param)
raise exception.RequiredParameterNotProvided(**kwargs)
- if value:
+ if value is not None:
value = self.param_type(value)
params[self.heat_param] = value
@@ -481,6 +482,9 @@ class AtomicSwarmTemplateDefinition(BaseTemplateDefinition):
def __init__(self):
super(AtomicSwarmTemplateDefinition, self).__init__()
+ self.add_parameter('bay_uuid',
+ bay_attr='uuid',
+ param_type=str)
self.add_parameter('number_of_nodes',
bay_attr='node_count',
param_type=str)
@@ -489,6 +493,9 @@ class AtomicSwarmTemplateDefinition(BaseTemplateDefinition):
self.add_parameter('external_network',
baymodel_attr='external_network_id',
required=True)
+ self.add_parameter('insecure',
+ baymodel_attr='insecure',
+ required=True)
self.add_output('swarm_master',
bay_attr='api_address')
self.add_output('swarm_nodes_external',
@@ -519,6 +526,12 @@ class AtomicSwarmTemplateDefinition(BaseTemplateDefinition):
def get_params(self, context, baymodel, bay, **kwargs):
extra_params = kwargs.pop('extra_params', {})
extra_params['discovery_url'] = self.get_discovery_url(bay)
+ # HACK(apmelton) - This uses the user's bearer token, ideally
+ # it should be replaced with an actual trust token with only
+ # access to do what the template needs it to do.
+ extra_params['user_token'] = context.auth_token
+ osc = clients.OpenStackClients(context)
+ extra_params['magnum_url'] = osc.magnum_url()
return super(AtomicSwarmTemplateDefinition,
self).get_params(context, baymodel, bay,
diff --git a/magnum/db/sqlalchemy/alembic/versions/1d045384b966_add_insecure_baymodel_attr.py b/magnum/db/sqlalchemy/alembic/versions/1d045384b966_add_insecure_baymodel_attr.py
new file mode 100644
index 0000000000..c971bfaca8
--- /dev/null
+++ b/magnum/db/sqlalchemy/alembic/versions/1d045384b966_add_insecure_baymodel_attr.py
@@ -0,0 +1,34 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""add-insecure-baymodel-attr
+
+Revision ID: 1d045384b966
+Revises: 1481f5b560dd
+Create Date: 2015-09-23 18:17:10.195121
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '1d045384b966'
+down_revision = '1481f5b560dd'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ insecure_column = sa.Column('insecure', sa.Boolean(), default=False)
+ op.add_column('baymodel', insecure_column)
+ baymodel = sa.sql.table('baymodel', insecure_column)
+ op.execute(
+ baymodel.update().values({'insecure': True})
+ )
diff --git a/magnum/db/sqlalchemy/models.py b/magnum/db/sqlalchemy/models.py
index 32147c45ad..a51d72911f 100644
--- a/magnum/db/sqlalchemy/models.py
+++ b/magnum/db/sqlalchemy/models.py
@@ -172,6 +172,7 @@ class BayModel(Base):
no_proxy = Column(String(255))
registry_enabled = Column(Boolean, default=False)
labels = Column(JSONEncodedDict)
+ insecure = Column(Boolean, default=False)
class Container(Base):
diff --git a/magnum/objects/baymodel.py b/magnum/objects/baymodel.py
index ca015adf54..168ff5cbbe 100644
--- a/magnum/objects/baymodel.py
+++ b/magnum/objects/baymodel.py
@@ -25,7 +25,8 @@ class BayModel(base.MagnumPersistentObject, base.MagnumObject,
# Version 1.1: Add 'registry_enabled' field
# Version 1.2: Added 'network_driver' field
# Version 1.3: Added 'labels' attribute
- VERSION = '1.3'
+ # Version 1.4: Added 'insecure' attribute
+ VERSION = '1.4'
dbapi = dbapi.get_instance()
@@ -52,7 +53,8 @@ class BayModel(base.MagnumPersistentObject, base.MagnumObject,
'https_proxy': fields.StringField(nullable=True),
'no_proxy': fields.StringField(nullable=True),
'registry_enabled': fields.BooleanField(default=False),
- 'labels': fields.DictOfStringsField(nullable=True)
+ 'labels': fields.DictOfStringsField(nullable=True),
+ 'insecure': fields.BooleanField(default=False),
}
@staticmethod
diff --git a/magnum/templates/docker-swarm/fragments/make_cert.py b/magnum/templates/docker-swarm/fragments/make_cert.py
new file mode 100644
index 0000000000..14b6adf0bc
--- /dev/null
+++ b/magnum/templates/docker-swarm/fragments/make_cert.py
@@ -0,0 +1,145 @@
+#!/usr/bin/python
+
+# Copyright 2015 Rackspace, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import os
+import subprocess
+
+import requests
+
+HEAT_PARAMS_PATH = '/etc/sysconfig/heat-params'
+PUBLIC_IP_URL = 'http://169.254.169.254/latest/meta-data/public-ipv4'
+CERT_DIR = '/etc/docker'
+CERT_CONF_DIR = '%s/conf' % CERT_DIR
+CA_CERT_PATH = '%s/ca.crt' % CERT_DIR
+SERVER_CONF_PATH = '%s/server.conf' % CERT_CONF_DIR
+SERVER_KEY_PATH = '%s/server.key' % CERT_DIR
+SERVER_CSR_PATH = '%s/server.csr' % CERT_DIR
+SERVER_CERT_PATH = '%s/server.crt' % CERT_DIR
+
+CSR_CONFIG_TEMPLATE = """
+[req]
+distinguished_name = req_distinguished_name
+req_extensions = req_ext
+x509_extensions = req_ext
+prompt = no
+copy_extensions = copyall
+[req_distinguished_name]
+CN = swarm.invalid
+[req_ext]
+subjectAltName = %(subject_alt_names)s
+extendedKeyUsage = clientAuth,serverAuth
+"""
+
+
+def _parse_config_value(value):
+ parsed_value = value
+ if parsed_value[-1] == '\n':
+ parsed_value = parsed_value[:-1]
+ return parsed_value[1:-1]
+
+
+def load_config():
+ config = dict()
+ with open(HEAT_PARAMS_PATH, 'r') as fp:
+ for line in fp.readlines():
+ key, value = line.split('=', 1)
+ config[key] = _parse_config_value(value)
+ return config
+
+
+def create_dirs():
+ os.makedirs(CERT_CONF_DIR)
+
+
+def _get_public_ip():
+ return requests.get(PUBLIC_IP_URL).text
+
+
+def _build_subject_alt_names(config):
+ subject_alt_names = [
+ 'IP:%s' % _get_public_ip(),
+ 'IP:%s' % config['SWARM_NODE_IP'],
+ 'IP:127.0.0.1'
+ ]
+ return ','.join(subject_alt_names)
+
+
+def write_ca_cert(config):
+ bay_cert_url = '%s/certificates/%s' % (config['MAGNUM_URL'],
+ config['BAY_UUID'])
+ headers = {'X-Auth-Token': config['USER_TOKEN']}
+ ca_cert_resp = requests.get(bay_cert_url,
+ headers=headers)
+
+ with open(CA_CERT_PATH, 'w') as fp:
+ fp.write(ca_cert_resp.json()['pem'])
+
+
+def write_server_key():
+ subprocess.call(['openssl', 'genrsa',
+ '-out', SERVER_KEY_PATH,
+ '4096'])
+
+
+def _write_csr_config(config):
+ with open(SERVER_CONF_PATH, 'w') as fp:
+ params = {
+ 'subject_alt_names': _build_subject_alt_names(config)
+ }
+ fp.write(CSR_CONFIG_TEMPLATE % params)
+
+
+def create_server_csr(config):
+ _write_csr_config(config)
+ subprocess.call(['openssl', 'req', '-new',
+ '-days', '1000',
+ '-key', SERVER_KEY_PATH,
+ '-out', SERVER_CSR_PATH,
+ '-reqexts', 'req_ext',
+ '-extensions', 'req_ext',
+ '-config', SERVER_CONF_PATH])
+
+ with open(SERVER_CSR_PATH, 'r') as fp:
+ return {'bay_uuid': config['BAY_UUID'], 'csr': fp.read()}
+
+
+def write_server_cert(config, csr_req):
+ cert_url = '%s/certificates' % config['MAGNUM_URL']
+ headers = {
+ 'Content-Type': 'application/json',
+ 'X-Auth-Token': config['USER_TOKEN']
+ }
+ csr_resp = requests.post(cert_url,
+ data=json.dumps(csr_req),
+ headers=headers)
+
+ with open(SERVER_CERT_PATH, 'w') as fp:
+ fp.write(csr_resp.json()['pem'])
+
+
+def main():
+ config = load_config()
+ if config['INSECURE'] == 'False':
+ create_dirs()
+ write_ca_cert(config)
+ write_server_key()
+ csr_req = create_server_csr(config)
+ write_server_cert(config, csr_req)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/magnum/templates/docker-swarm/fragments/write-docker-service.sh b/magnum/templates/docker-swarm/fragments/write-docker-service.sh
new file mode 100644
index 0000000000..7fe29a00b4
--- /dev/null
+++ b/magnum/templates/docker-swarm/fragments/write-docker-service.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+. /etc/sysconfig/heat-params
+
+mkdir -p /etc/systemd/system/docker.service.d
+
+cat > /etc/systemd/system/docker.service << END_SERVICE_TOP
+[Unit]
+Description=Docker Application Container Engine
+Documentation=http://docs.docker.com
+After=network.target docker.socket
+Requires=docker.socket
+
+[Service]
+Type=notify
+EnvironmentFile=-/etc/sysconfig/docker
+EnvironmentFile=-/etc/sysconfig/docker-storage
+EnvironmentFile=-/etc/sysconfig/docker-network
+ExecStart=/usr/bin/docker -d -H fd:// \\
+ -H tcp://0.0.0.0:2375 \\
+END_SERVICE_TOP
+
+if [ $INSECURE == 'False' ]; then
+
+cat >> /etc/systemd/system/docker.service << END_TLS
+ --tls \\
+ --tlsverify \\
+ --tlscacert="/etc/docker/ca.crt" \\
+ --tlskey="/etc/docker/server.key" \\
+ --tlscert="/etc/docker/server.crt" \\
+END_TLS
+
+fi
+
+cat >> /etc/systemd/system/docker.service << END_SERVICE_BOTTOM
+ \$OPTIONS \\
+ \$DOCKER_STORAGE_OPTIONS \\
+ \$DOCKER_NETWORK_OPTIONS \\
+ \$INSECURE_REGISTRY
+LimitNOFILE=1048576
+LimitNPROC=1048576
+LimitCORE=infinity
+MountFlags=slave
+
+[Install]
+WantedBy=multi-user.target
+END_SERVICE_BOTTOM
+
+chown root:root /etc/systemd/system/docker.service
+chmod 644 /etc/systemd/system/docker.service
diff --git a/magnum/templates/docker-swarm/fragments/write-docker-service.yaml b/magnum/templates/docker-swarm/fragments/write-docker-service.yaml
deleted file mode 100644
index 6444e70b03..0000000000
--- a/magnum/templates/docker-swarm/fragments/write-docker-service.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-#cloud-config
-merge_how: dict(recurse_array)+list(append)
-bootcmd:
- - mkdir -p /etc/systemd/system/docker.service.d
-write_files:
- - path: /etc/systemd/system/docker.service
- owner: "root:root"
- permissions: "0644"
- content: |
- [Unit]
- Description=Docker Application Container Engine
- Documentation=http://docs.docker.com
- After=network.target docker.socket
- Requires=docker.socket
-
- [Service]
- Type=notify
- EnvironmentFile=-/etc/sysconfig/docker
- EnvironmentFile=-/etc/sysconfig/docker-storage
- EnvironmentFile=-/etc/sysconfig/docker-network
- ExecStart=/usr/bin/docker -d -H fd:// \
- $OPTIONS \
- $DOCKER_STORAGE_OPTIONS \
- $DOCKER_NETWORK_OPTIONS \
- $INSECURE_REGISTRY
- LimitNOFILE=1048576
- LimitNPROC=1048576
- LimitCORE=infinity
- MountFlags=slave
-
- [Install]
- WantedBy=multi-user.target
diff --git a/magnum/templates/docker-swarm/fragments/write-docker-tcp-socket.yaml b/magnum/templates/docker-swarm/fragments/write-docker-tcp-socket.yaml
deleted file mode 100644
index 2ba057b949..0000000000
--- a/magnum/templates/docker-swarm/fragments/write-docker-tcp-socket.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-#cloud-config
-merge_how: dict(recurse_array)+list(append)
-write_files:
- - path: /etc/systemd/system/docker-tcp.socket
- owner: "root:root"
- permissions: "0644"
- content: |
- [Unit]
- Description=Docker Socket for the API
- PartOf=docker.service
-
- [Socket]
- ListenStream=2375
- BindIPv6Only=both
- Service=docker.service
-
- [Install]
- WantedBy=sockets.target
diff --git a/magnum/templates/docker-swarm/fragments/write-heat-params.yaml b/magnum/templates/docker-swarm/fragments/write-heat-params.yaml
index 61c7f68c45..0c83f587e2 100644
--- a/magnum/templates/docker-swarm/fragments/write-heat-params.yaml
+++ b/magnum/templates/docker-swarm/fragments/write-heat-params.yaml
@@ -10,3 +10,8 @@ write_files:
HTTPS_PROXY="$HTTPS_PROXY"
NO_PROXY="$NO_PROXY"
SWARM_MASTER_IP="$SWARM_MASTER_IP"
+ SWARM_NODE_IP="$SWARM_NODE_IP"
+ BAY_UUID="$BAY_UUID"
+ USER_TOKEN="$USER_TOKEN"
+ MAGNUM_URL="$MAGNUM_URL"
+ INSECURE="$INSECURE"
diff --git a/magnum/templates/docker-swarm/fragments/write-swarm-master-service.sh b/magnum/templates/docker-swarm/fragments/write-swarm-master-service.sh
new file mode 100644
index 0000000000..6a3920e5d8
--- /dev/null
+++ b/magnum/templates/docker-swarm/fragments/write-swarm-master-service.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+cat > /etc/systemd/system/swarm-manager.service << END_SERVICE_TOP
+[Unit]
+Description=Swarm Manager
+After=docker.service
+Requires=docker.service
+
+[Service]
+TimeoutStartSec=0
+ExecStartPre=-/usr/bin/docker kill swarm-manager
+ExecStartPre=-/usr/bin/docker rm swarm-manager
+ExecStartPre=/usr/bin/docker pull swarm:0.2.0
+#TODO: roll-back from swarm:0.2.0 to swarm if atomic image can work with latest swarm image
+ExecStart=/usr/bin/docker run --name swarm-manager \\
+ -v /etc/docker:/etc/docker \\
+ -p 2376:2375 \\
+ -e http_proxy=$HTTP_PROXY \\
+ -e https_proxy=$HTTPS_PROXY \\
+ -e no_proxy=$NO_PROXY \\
+ swarm:0.2.0 \\
+ manage -H tcp://0.0.0.0:2375 \\
+END_SERVICE_TOP
+
+if [ $INSECURE = 'False' ]; then
+
+cat >> /etc/systemd/system/swarm-manager.service << END_TLS
+ --tls \\
+ --tlsverify \\
+ --tlscacert=/etc/docker/ca.crt \\
+ --tlskey=/etc/docker/server.key \\
+ --tlscert=/etc/docker/server.crt \\
+END_TLS
+
+fi
+
+cat >> /etc/systemd/system/swarm-manager.service << END_SERVICE_BOTTOM
+ $DISCOVERY_URL
+ExecStop=/usr/bin/docker stop swarm-manager
+ExecStartPost=/usr/bin/curl -sf -X PUT -H 'Content-Type: application/json' \\
+ --data-binary '{"Status": "SUCCESS", "Reason": "Setup complete", "Data": "OK", "UniqueId": "00000"}' \\
+ "$WAIT_HANDLE"
+
+[Install]
+WantedBy=multi-user.target
+END_SERVICE_BOTTOM
+
+chown root:root /etc/systemd/system/swarm-manager.service
+chmod 644 /etc/systemd/system/swarm-manager.service
diff --git a/magnum/templates/docker-swarm/fragments/write-swarm-master-service.yaml b/magnum/templates/docker-swarm/fragments/write-swarm-master-service.yaml
deleted file mode 100644
index 0fe438a23c..0000000000
--- a/magnum/templates/docker-swarm/fragments/write-swarm-master-service.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-#cloud-config
-merge_how: dict(recurse_array)+list(append)
-write_files:
- - path: /etc/systemd/system/swarm-manager.service
- owner: "root:root"
- permissions: "0644"
- content: |
- [Unit]
- Description=Swarm Manager
- After=docker.service
- Requires=docker.service
-
- [Service]
- TimeoutStartSec=0
- ExecStartPre=-/usr/bin/docker kill swarm-manager
- ExecStartPre=-/usr/bin/docker rm swarm-manager
- ExecStartPre=/usr/bin/docker pull swarm:0.2.0
- #TODO: roll-back from swarm:0.2.0 to swarm if atomic image can work with latest swarm image
- ExecStart=/usr/bin/docker run -e http_proxy=$HTTP_PROXY -e https_proxy=$HTTPS_PROXY -e no_proxy=$NO_PROXY --name swarm-manager -p 2376:2375 swarm:0.2.0 manage -H tcp://0.0.0.0:2375 $DISCOVERY_URL
- ExecStop=/usr/bin/docker stop swarm-manager
- ExecStartPost=/usr/bin/curl -sf -X PUT -H 'Content-Type: application/json' \
- --data-binary '{"Status": "SUCCESS", "Reason": "Setup complete", "Data": "OK", "UniqueId": "00000"}' \
- "$WAIT_HANDLE"
-
- [Install]
- WantedBy=multi-user.target
diff --git a/magnum/templates/docker-swarm/swarm.yaml b/magnum/templates/docker-swarm/swarm.yaml
index 3204624deb..caaac18e20 100644
--- a/magnum/templates/docker-swarm/swarm.yaml
+++ b/magnum/templates/docker-swarm/swarm.yaml
@@ -25,6 +25,18 @@ parameters:
type: string
description: url provided for node discovery
+ user_token:
+ type: string
+ description: token used for communicating back to Magnum for TLS certs
+
+ bay_uuid:
+ type: string
+ description: identifier for the bay this template is generating
+
+ magnum_url:
+ type: string
+ description: endpoint to retrieve TLS certs from
+
#
# OPTIONAL PARAMETERS
#
@@ -68,6 +80,11 @@ parameters:
description: network range for fixed ip network
default: "10.0.0.0/24"
+ insecure:
+ type: boolean
+ description: whether or not to enable TLS
+ default: False
+
resources:
master_wait_handle:
@@ -171,6 +188,11 @@ resources:
"$HTTPS_PROXY": {get_param: https_proxy}
"$NO_PROXY": {get_param: no_proxy}
"$SWARM_MASTER_IP": {get_attr: [swarm_master_eth0, fixed_ips, 0, ip_address]}
+ "$SWARM_NODE_IP": {get_attr: [swarm_master_eth0, fixed_ips, 0, ip_address]}
+ "$BAY_UUID": {get_param: bay_uuid}
+ "$USER_TOKEN": {get_param: user_token}
+ "$MAGNUM_URL": {get_param: magnum_url}
+ "$INSECURE": {get_param: insecure}
remove_docker_key:
type: "OS::Heat::SoftwareConfig"
@@ -178,11 +200,17 @@ resources:
group: ungrouped
config: {get_file: fragments/remove-docker-key.sh}
+ make_cert:
+ type: "OS::Heat::SoftwareConfig"
+ properties:
+ group: ungrouped
+ config: {get_file: fragments/make_cert.py}
+
write_docker_service:
type: "OS::Heat::SoftwareConfig"
properties:
group: ungrouped
- config: {get_file: fragments/write-docker-service.yaml}
+ config: {get_file: fragments/write-docker-service.sh}
write_docker_socket:
type: "OS::Heat::SoftwareConfig"
@@ -190,12 +218,6 @@ resources:
group: ungrouped
config: {get_file: fragments/write-docker-socket.yaml}
- write_docker_tcp_socket:
- type: "OS::Heat::SoftwareConfig"
- properties:
- group: ungrouped
- config: {get_file: fragments/write-docker-tcp-socket.yaml}
-
write_swarm_agent_service:
type: "OS::Heat::SoftwareConfig"
properties:
@@ -217,13 +239,14 @@ resources:
group: ungrouped
config:
str_replace:
- template: {get_file: fragments/write-swarm-master-service.yaml}
+ template: {get_file: fragments/write-swarm-master-service.sh}
params:
"$DISCOVERY_URL": {get_param: discovery_url}
"$WAIT_HANDLE": {get_resource: master_wait_handle}
"$HTTP_PROXY": {get_param: http_proxy}
"$HTTPS_PROXY": {get_param: https_proxy}
"$NO_PROXY": {get_param: no_proxy}
+ "$INSECURE": {get_param: insecure}
enable_services:
type: "OS::Heat::SoftwareConfig"
@@ -261,9 +284,9 @@ resources:
- config: {get_resource: remove_docker_key}
- config: {get_resource: write_heat_params}
- config: {get_resource: add_proxy}
+ - config: {get_resource: make_cert}
- config: {get_resource: write_docker_service}
- config: {get_resource: write_docker_socket}
- - config: {get_resource: write_docker_tcp_socket}
- config: {get_resource: write_swarm_agent_service}
- config: {get_resource: write_swarm_master_service}
- config: {get_resource: enable_services}
@@ -333,6 +356,10 @@ resources:
https_proxy: {get_param: https_proxy}
no_proxy: {get_param: no_proxy}
swarm_master_ip: {get_attr: [swarm_master_eth0, fixed_ips, 0, ip_address]}
+ bay_uuid: {get_param: bay_uuid}
+ user_token: {get_param: user_token}
+ magnum_url: {get_param: magnum_url}
+ insecure: {get_param: insecure}
outputs:
diff --git a/magnum/templates/docker-swarm/swarmnode.yaml b/magnum/templates/docker-swarm/swarmnode.yaml
index 2285934488..f10e723750 100644
--- a/magnum/templates/docker-swarm/swarmnode.yaml
+++ b/magnum/templates/docker-swarm/swarmnode.yaml
@@ -57,6 +57,22 @@ parameters:
type: string
description: swarm master's ip address
+ user_token:
+ type: string
+ description: token used for communicating back to Magnum for TLS certs
+
+ bay_uuid:
+ type: string
+ description: identifier for the bay this template is generating
+
+ magnum_url:
+ type: string
+ description: endpoint to retrieve TLS certs from
+
+ insecure:
+ type: boolean
+ description: whether or not to disable TLS
+
resources:
node_wait_handle:
@@ -119,6 +135,11 @@ resources:
"$HTTPS_PROXY": {get_param: https_proxy}
"$NO_PROXY": {get_param: no_proxy}
"$SWARM_MASTER_IP": {get_param: swarm_master_ip}
+ "$SWARM_NODE_IP": {get_attr: [swarm_node_eth0, fixed_ips, 0, ip_address]}
+ "$BAY_UUID": {get_param: bay_uuid}
+ "$USER_TOKEN": {get_param: user_token}
+ "$MAGNUM_URL": {get_param: magnum_url}
+ "$INSECURE": {get_param: insecure}
remove_docker_key:
type: "OS::Heat::SoftwareConfig"
@@ -126,11 +147,17 @@ resources:
group: ungrouped
config: {get_file: fragments/remove-docker-key.sh}
+ make_cert:
+ type: "OS::Heat::SoftwareConfig"
+ properties:
+ group: ungrouped
+ config: {get_file: fragments/make_cert.py}
+
write_docker_service:
type: "OS::Heat::SoftwareConfig"
properties:
group: ungrouped
- config: {get_file: fragments/write-docker-service.yaml}
+ config: {get_file: fragments/write-docker-service.sh}
write_docker_socket:
type: "OS::Heat::SoftwareConfig"
@@ -138,12 +165,6 @@ resources:
group: ungrouped
config: {get_file: fragments/write-docker-socket.yaml}
- write_docker_tcp_socket:
- type: "OS::Heat::SoftwareConfig"
- properties:
- group: ungrouped
- config: {get_file: fragments/write-docker-tcp-socket.yaml}
-
write_swarm_agent_service:
type: "OS::Heat::SoftwareConfig"
properties:
@@ -194,11 +215,11 @@ resources:
- config: {get_resource: disable_selinux}
- config: {get_resource: remove_docker_key}
- config: {get_resource: write_heat_params}
+ - config: {get_resource: make_cert}
- config: {get_resource: add_proxy}
- config: {get_resource: write_swarm_agent_service}
- config: {get_resource: write_docker_service}
- config: {get_resource: write_docker_socket}
- - config: {get_resource: write_docker_tcp_socket}
- config: {get_resource: enable_services}
- config: {get_resource: cfn_signal}
diff --git a/magnum/tests/unit/conductor/handlers/test_bay_conductor.py b/magnum/tests/unit/conductor/handlers/test_bay_conductor.py
index df3f02f65b..011babc2bf 100644
--- a/magnum/tests/unit/conductor/handlers/test_bay_conductor.py
+++ b/magnum/tests/unit/conductor/handlers/test_bay_conductor.py
@@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
from heatclient import exc
from oslo_service import loopingcall
@@ -795,18 +796,51 @@ class TestHandler(db_base.DbTestCase):
bay = objects.Bay.get(self.context, self.bay.uuid)
self.assertEqual(bay.node_count, 1)
- @patch('magnum.conductor.handlers.common.cert_manager.'
- 'generate_certificates_to_bay')
+ @patch('magnum.conductor.handlers.bay_conductor.HeatPoller')
+ @patch('magnum.conductor.handlers.bay_conductor.cert_manager')
+ @patch('magnum.conductor.handlers.bay_conductor._create_stack')
+ @patch('magnum.conductor.handlers.bay_conductor.uuid')
+ @patch('magnum.common.clients.OpenStackClients')
+ def test_create(self, mock_openstack_client_class, mock_uuid,
+ mock_create_stack, mock_cert_manager,
+ mock_heat_poller_class):
+ timeout = 15
+ test_uuid = uuid.uuid4()
+ mock_uuid.uuid4.return_value = test_uuid
+ mock_poller = mock.MagicMock()
+ mock_poller.poll_and_check.return_value = loopingcall.LoopingCallDone()
+ mock_heat_poller_class.return_value = mock_poller
+ mock_openstack_client_class.return_value = mock.sentinel.osc
+
+ def create_stack_side_effect(context, osc, bay, timeout):
+ self.assertEqual(bay.uuid, str(test_uuid))
+ return {'stack': {'id': 'stack-id'}}
+
+ mock_create_stack.side_effect = create_stack_side_effect
+
+ self.handler.bay_create(self.context,
+ self.bay, timeout)
+
+ mock_create_stack.assert_called_once_with(self.context,
+ mock.sentinel.osc,
+ self.bay, timeout)
+ mock_cert_manager.generate_certificates_to_bay.assert_called_once_with(
+ self.bay)
+
+ @patch('magnum.conductor.handlers.bay_conductor.cert_manager')
@patch('magnum.conductor.handlers.bay_conductor._create_stack')
@patch('magnum.common.clients.OpenStackClients')
- def test_create(self, mock_openstack_client_class, mock_create_stack,
- mock_generate_certificates):
+ def test_create_handles_bad_request(self, mock_openstack_client_class,
+ mock_create_stack,
+ mock_cert_manager):
mock_create_stack.side_effect = exc.HTTPBadRequest
timeout = 15
self.assertRaises(exception.InvalidParameterValue,
self.handler.bay_create, self.context,
self.bay, timeout)
- mock_generate_certificates.assert_called_once_with(self.bay)
+ mock_cert_manager.generate_certificates_to_bay.assert_called_once_with(
+ self.bay)
+ mock_cert_manager.delete_certificates_from_bay(self.bay)
@patch('magnum.common.clients.OpenStackClients')
def test_bay_delete(self, mock_openstack_client_class):
@@ -833,7 +867,8 @@ class TestBayConductorWithSwarm(base.TestCase):
'coe': 'swarm',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
- 'no_proxy': 'no_proxy'
+ 'no_proxy': 'no_proxy',
+ 'insecure': False
}
self.bay_dict = {
'id': 1,
@@ -846,6 +881,12 @@ class TestBayConductorWithSwarm(base.TestCase):
'node_count': 1,
'discovery_url': 'token://39987da72f8386e0d0225ae8929e7cb4',
}
+ osc_patcher = mock.patch('magnum.common.clients.OpenStackClients')
+ self.mock_osc_class = osc_patcher.start()
+ self.addCleanup(osc_patcher.stop)
+ self.mock_osc = mock.MagicMock()
+ self.mock_osc.magnum_url.return_value = 'http://127.0.0.1:9511/v1'
+ self.mock_osc_class.return_value = self.mock_osc
@patch('magnum.objects.BayModel.get_by_uuid')
def test_extract_template_definition_all_values(
@@ -870,7 +911,12 @@ class TestBayConductorWithSwarm(base.TestCase):
'discovery_url': 'token://39987da72f8386e0d0225ae8929e7cb4',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
- 'no_proxy': 'no_proxy'
+ 'no_proxy': 'no_proxy',
+ 'user_token': self.context.auth_token,
+ 'bay_uuid': 'some_uuid',
+ 'magnum_url': self.mock_osc.magnum_url.return_value,
+ 'insecure': False
+
}
self.assertEqual(expected, definition)
@@ -901,7 +947,11 @@ class TestBayConductorWithSwarm(base.TestCase):
'ssh_key_name': 'keypair_id',
'external_network': 'external_network_id',
'number_of_nodes': '1',
- 'discovery_url': 'test_discovery'
+ 'discovery_url': 'test_discovery',
+ 'user_token': self.context.auth_token,
+ 'bay_uuid': 'some_uuid',
+ 'magnum_url': self.mock_osc.magnum_url.return_value,
+ 'insecure': False
}
self.assertEqual(expected, definition)
diff --git a/magnum/tests/unit/db/utils.py b/magnum/tests/unit/db/utils.py
index 4fff74cb23..9ccacbd172 100644
--- a/magnum/tests/unit/db/utils.py
+++ b/magnum/tests/unit/db/utils.py
@@ -52,7 +52,8 @@ def get_test_baymodel(**kw):
'http_proxy': kw.get('http_proxy', 'fake_http_proxy'),
'https_proxy': kw.get('https_proxy', 'fake_https_proxy'),
'no_proxy': kw.get('no_proxy', 'fake_no_proxy'),
- 'registry_enabled': kw.get('registry_enabled', False)
+ 'registry_enabled': kw.get('registry_enabled', False),
+ 'insecure': kw.get('insecure', False)
}
diff --git a/magnum/tests/unit/objects/test_objects.py b/magnum/tests/unit/objects/test_objects.py
index 2cac215055..dde6c75698 100644
--- a/magnum/tests/unit/objects/test_objects.py
+++ b/magnum/tests/unit/objects/test_objects.py
@@ -426,7 +426,7 @@ class _TestObject(object):
object_data = {
'Bay': '1.0-35edde13ad178e9419e7ea8b6d580bcd',
'BayLock': '1.0-7d1eb08cf2070523bd210369c7a2e076',
- 'BayModel': '1.3-369d7b7f05720780ae4f6c5d983e8c3e',
+ 'BayModel': '1.4-68d7979ff1d81f948180fb620e6f84c7',
'Certificate': '1.0-2aff667971b85c1edf8d15684fd7d5e2',
'Container': '1.0-e12affbba5f8a748882a3ae98aced282',
'MyObj': '1.0-b43567e512438205e32f4e95ca616697',