diff options
author | Robert Gemmell <robbie@apache.org> | 2013-04-12 16:16:09 +0000 |
---|---|---|
committer | Robert Gemmell <robbie@apache.org> | 2013-04-12 16:16:09 +0000 |
commit | 249369d22526b77b3ffa4c456854b55c287cfd7b (patch) | |
tree | d3706c9c525d196e824d1fdd51873ec275295eae /qpid/java | |
parent | 332410c66c62d5e075e9f9077d29fc4669e11db0 (diff) | |
download | qpid-python-249369d22526b77b3ffa4c456854b55c287cfd7b.tar.gz |
QPID-4739: complete support for defining multiple key/trust stores and assigning them on a port-specific basis
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1467334 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java')
56 files changed, 2523 insertions, 701 deletions
diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java index 2f51e30b57..3cc382596a 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java @@ -62,6 +62,7 @@ import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.Session; import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.User; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.adapter.AbstractPluginAdapter; @@ -240,7 +241,7 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem } else if (protocols.contains(Protocol.HTTPS)) { - KeyStore keyStore = _broker.getDefaultKeyStore(); + KeyStore keyStore = port.getKeyStore(); if (keyStore == null) { throw new IllegalConfigurationException("Key store is not configured. Cannot start management on HTTPS port without keystore"); @@ -290,6 +291,8 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem addRestServlet(root, "binding", VirtualHost.class, Exchange.class, Queue.class, Binding.class); addRestServlet(root, "port", Port.class); addRestServlet(root, "session", VirtualHost.class, Connection.class, Session.class); + addRestServlet(root, "keystore", KeyStore.class); + addRestServlet(root, "truststore", TrustStore.class); root.addServlet(new ServletHolder(new StructureServlet()), "/rest/structure"); root.addServlet(new ServletHolder(new MessageServlet()), "/rest/message/*"); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addAuthenticationProvider.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addAuthenticationProvider.html index 90dd1f1090..f4846ac556 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/addAuthenticationProvider.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addAuthenticationProvider.html @@ -15,15 +15,16 @@ ~ limitations under the License. --> <div class="dijitHidden"> - <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Authentication Provider'" id="addAuthenticationProvider"> + <div data-dojo-type="dijit.Dialog" data-dojo-props="title:'Authentication Provider'" id="addAuthenticationProvider"> <form id="formAddAuthenticationProvider" method="post" dojoType="dijit.form.Form"> + <div style="height:100px; width:420px; overflow: auto"> <table class="tableContainer-table tableContainer-table-horiz" width="100%" cellspacing="1"> <tr> - <td class="tableContainer-labelCell" style="width: 300px;">Type*:</td> + <td class="tableContainer-labelCell" style="width: 200px;">Type*:</td> <td class="tableContainer-valueCell"><div id="addAuthenticationProvider.selectAuthenticationProviderDiv"></div></td> </tr> <tr> - <td class="tableContainer-labelCell" style="width: 300px;">Name*:</td> + <td class="tableContainer-labelCell" style="width: 200px;">Name*:</td> <td class="tableContainer-valueCell"><input type="text" required="true" name="name" id="formAddAuthenticationProvider.name" placeholder="Name" regexp="^[\x20-\x2e\x30-\x7F]{1,255}$" dojoType="dijit.form.ValidationTextBox" missingMessage="A name must be supplied" /></div></td> @@ -31,8 +32,11 @@ </table> <input type="hidden" id="formAddAuthenticationProvider.id" name="id"/> <div id="addAuthenticationProvider.fieldSets"></div> + </div> + <div class="dijitDialogPaneActionBar"> <!-- submit buttons --> <input type="submit" value="Save Authentication Provider" label="Save Authentication Provider" dojoType="dijit.form.Button" /> + </div> </form> </div> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addBinding.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addBinding.html index 8dbd219c8d..9aebca90d7 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/addBinding.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addBinding.html @@ -32,11 +32,10 @@ dojoType="dijit.form.ValidationTextBox" missingMessage="A name must be supplied" /></td> </tr> </table> - <br/> - + <div class="dijitDialogPaneActionBar"> <!-- submit buttons --> <input type="submit" value="Create Binding" label="Create Binding" dojoType="dijit.form.Button" /> - + </div> </form> </div> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addExchange.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addExchange.html index 4a59cd2cbc..8c9968e37a 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/addExchange.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addExchange.html @@ -44,11 +44,10 @@ </td> </tr> </table> - <br/> - + <div class="dijitDialogPaneActionBar"> <!-- submit buttons --> <input type="submit" value="Create Exchange" label="Create Exchange" dojoType="dijit.form.Button" /> - + </div> </form> </div> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addPort.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addPort.html index c37b879bd5..391783c6d8 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/addPort.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addPort.html @@ -19,9 +19,9 @@ - --> <div class="dijitHidden"> - <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Port'" id="addPort"> + <div data-dojo-type="dijit.Dialog" data-dojo-props="title:'Port'" id="addPort"> <form id="formAddPort" method="post" dojoType="dijit.form.Form"> - <div class="dijitDialogPaneContentArea"> + <div style="height:320px; width:420px; overflow: auto"> <div id="formAddPort:fields"> <input type="text" required="true" name="name" id="formAddPort.name" placeholder="Name" data-dojo-props="label: 'Name*:'" dojoType="dijit.form.ValidationTextBox" @@ -29,13 +29,7 @@ <input data-dojo-type="dijit.form.NumberSpinner" id="formAddPort.port" required="true" data-dojo-props="label: 'Port Number*:'" name="port" smallDelta="1" constraints="{min:1,max:65535,places:0, pattern: '#####'}" missingMessage="A port number must be supplied" /> - <select id="formAddPort.transports" data-dojo-type="dijit.form.FilteringSelect" - data-dojo-props="name: 'transports',label: 'Transport:',searchAttr: 'name',required:false,placeHolder: 'TCP', value: '' " - style="margin: 0;"> - <option value="TCP">TCP</option> - <option value="SSL">SSL</option> - </select> - <select id="formAddPort.authenticationProvider" data-dojo-type="dijit.form.FilteringSelect" style="margin: 0;" + <select id="formAddPort.authenticationProvider" data-dojo-type="dijit.form.FilteringSelect" data-dojo-props="name:'authenticationProvider',label:'Authentication Provider:', searchAttr: 'name', required: false, placeHolder: 'Default', value: '' "> </select> <select id="formAddPort.type" data-dojo-type="dijit.form.FilteringSelect" @@ -45,17 +39,11 @@ <option value="HTTP">HTTP</option> </select> </div> - <div id="formAddPort:fieldsClientAuth"> - <input id="formAddPort.needClientAuth" type="checkbox" name="needClientAuth" - dojoType="dijit.form.CheckBox" data-dojo-props="label: 'Need SSL Client Certificate:'"/> - <input id="formAddPort.wantClientAuth" type="checkbox" name="wantClientAuth" - dojoType="dijit.form.CheckBox" data-dojo-props="label: 'Want SSL Client Certificate:'"/> - </div> <div id="formAddPort:fieldsAMQP"> <input id="formAddPort.bindingAddress" type="text" name="bindingAddress" placeholder="*" dojoType="dijit.form.TextBox" data-dojo-props="label: 'Binding address:'"/> <input id="formAddPort.protocolsDefault" type="checkbox" checked="checked" - dojoType="dijit.form.CheckBox" data-dojo-props="label: 'Support broker default AMQP versions:'"/> + dojoType="dijit.form.CheckBox" data-dojo-props="label: 'Support default protocols:'"/> <select id="formAddPort.protocolsAMQP" name="protocols" data-dojo-type="dijit.form.MultiSelect" multiple="true" data-dojo-props="name: 'protocols', value: '', placeHolder: 'Select AMQP versions', label: 'AMQP versions:'" missingMessage="AMQP protocol(s) must be supplied"> @@ -80,6 +68,37 @@ <option value="HTTPS">HTTPS</option> </select> </div> + <div id="formAddPort:transport" > + <select id="formAddPort.transports" data-dojo-type="dijit.form.FilteringSelect" + data-dojo-props="name: 'transports',label: 'Transport:',searchAttr: 'name',required:false,placeHolder: 'TCP', value: '' " + style="margin: 0;"> + <option value="TCP">TCP</option> + <option value="SSL">SSL</option> + </select> + </div> + <div id="formAddPort:fieldsTransportSSL"> + <select id="formAddPort.keyStore" data-dojo-type="dijit.form.FilteringSelect" + data-dojo-props="name:'keyStore',label:'Key Store*:', searchAttr: 'name', placeHolder: 'Select keystore', value: '', required: true "> + </select> + </div> + <div id="formAddPort:fieldsClientAuth"> + <div id="formAddPort:fieldsClientAuth2"> + <input id="formAddPort.needClientAuth" type="checkbox" name="needClientAuth" + dojoType="dijit.form.CheckBox" data-dojo-props="label: 'Need SSL Client Certificate:'" /> + <input id="formAddPort.wantClientAuth" type="checkbox" name="wantClientAuth" + dojoType="dijit.form.CheckBox" data-dojo-props="label: 'Want SSL Client Certificate:'" /> + </div> + <div><strong>Trust Stores:</strong></div> + <table id="formAddPort.trustStores" data-dojo-type="dojox.grid.EnhancedGrid" + data-dojo-props="label:'Trust Stores:',plugins:{indirectSelection: true},rowSelector:'0px' " style="height: 100px"> + <thead> + <tr> + <th field="name">Name</th> + <th field="peersOnly">Peers Only</th> + </tr> + </thead> + </table> + </div> <input type="hidden" id="formAddPort.id" name="id"/> </div> <div class="dijitDialogPaneActionBar"> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addQueue.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addQueue.html index 950809d5fc..90a0af7ea9 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/addQueue.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addQueue.html @@ -19,8 +19,9 @@ - --> <div class="dijitHidden"> - <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Add Queue'" id="addQueue"> + <div data-dojo-type="dijit.Dialog" data-dojo-props="title:'Add Queue'" id="addQueue"> <form id="formAddQueue" method="post" dojoType="dijit.form.Form"> + <div style="height:250px; width:600px; overflow: auto"> <table cellpadding="0" cellspacing="2"> <tr> <td valign="top"><strong>Queue Name*: </strong></td> @@ -173,10 +174,11 @@ </tr> </table> </div> - <br/> + </div> + <div class="dijitDialogPaneActionBar"> <!-- submit buttons --> <input type="submit" value="Create Queue" label="Create Queue" dojoType="dijit.form.Button" /> - + </div> </form> </div> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHost.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHost.html index 9b492ef26d..43281f600d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHost.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHost.html @@ -62,10 +62,11 @@ </div> </div> - <br/> + <div class="dijitDialogPaneActionBar"> <!-- submit buttons --> <input type="submit" value="Save" label="Save" dojoType="dijit.form.Button" /> <input type="hidden" id="formAddVirtualHost.id" name="id"/> + </div> </form> </div> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html index a9cb580103..c4fbe77b08 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html @@ -24,6 +24,7 @@ <link rel="stylesheet" href="dojo/dojox/grid/resources/claroGrid.css"> <link rel="stylesheet" href="dojo/dojox/grid/enhanced/resources/claro/EnhancedGrid.css"> <link rel="stylesheet" href="dojo/dojox/grid/enhanced/resources/EnhancedGrid_rtl.css"> + <link rel="stylesheet" href="dojo/dojox/form/resources/CheckedMultiSelect.css"> <link rel="stylesheet" href="css/common.css" media="screen"> <script> function getContextPath() diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js index 5ff208d43f..77ae1ccf47 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js @@ -33,7 +33,7 @@ define(["dojo/_base/xhr", "dijit/form/RadioButton", "dijit/form/CheckBox", "dojox/layout/TableContainer", - "dojox/layout/ScrollPane", + "dijit/layout/ContentPane", "dojox/validate/us", "dojox/validate/web", "dojo/domReady!" @@ -141,7 +141,7 @@ define(["dojo/_base/xhr", return (type === "PlainPasswordFile" || type === "Base64MD5PasswordFile"); }; - util.showSetAttributesDialog = function(attributeWidgetFactories, data, putURL, dialogTitle) + util.showSetAttributesDialog = function(attributeWidgetFactories, data, putURL, dialogTitle, appendNameToUrl) { var layout = new dojox.layout.TableContainer({ cols: 1, @@ -154,7 +154,7 @@ define(["dojo/_base/xhr", var form = new dijit.form.Form(); var dialogContent = dom.create("div"); - var dialogContentArea = dom.create("div", { "class": "dijitDialogPaneContentArea"}); + var dialogContentArea = dom.create("div", {"style": {width: 600}}); var dialogActionBar = dom.create("div", { "class": "dijitDialogPaneActionBar"} ); dialogContent.appendChild(dialogContentArea); dialogContent.appendChild(dialogActionBar); @@ -171,11 +171,17 @@ define(["dojo/_base/xhr", var widget = attributeWidgetFactory.createWidget(data); var name = attributeWidgetFactory.name ? attributeWidgetFactory.name : widget.name; widgets[name] = widget; - widget.initialValue = widget.value; var dotPos = name.indexOf("."); if (dotPos == -1) { - layout.addChild(widget); + if (widget instanceof dijit.layout.ContentPane) + { + dialogContentArea.appendChild(widget.domNode); + } + else + { + layout.addChild(widget); + } } else { @@ -197,7 +203,7 @@ define(["dojo/_base/xhr", groups[groupName] = groupFieldContainer; var groupTitle = attributeWidgetFactory.groupName ? attributeWidgetFactory.groupName : groupName.charAt(0).toUpperCase() + groupName.slice(1); - var panel = new dijit.TitlePane({title: groupTitle, toggleable: false, content: groupFieldContainer.domNode}); + var panel = new dijit.TitlePane({title: groupTitle, content: groupFieldContainer.domNode}); dialogContentArea.appendChild(dom.create("br")); dialogContentArea.appendChild(panel.domNode); } @@ -224,8 +230,7 @@ define(["dojo/_base/xhr", } var setAttributesDialog = new dijit.Dialog({ title: dialogTitle, - content: form, - style: "width: 600px; max-height: 80%" + content: form }); form.on("submit", function(e) { @@ -235,23 +240,29 @@ define(["dojo/_base/xhr", if(form.validate()) { var values = {}; - for(var i in widgets) + var formWidgets = form.getDescendants(); + for(var i in formWidgets) { - var widget = widgets[i]; + var widget = formWidgets[i]; var value = widget.value; var propName = widget.name; - if ((widget instanceof dijit.form.CheckBox || widget instanceof dijit.form.RadioButton)) - { - values[ propName ] = widget.checked; - } - else if (value != widget.initialValue) - { - values[ propName ] = value ? value: null; + if (propName && !widget.disabled){ + if ((widget instanceof dijit.form.CheckBox || widget instanceof dijit.form.RadioButton)) { + if (widget.checked != widget.initialValue) { + values[ propName ] = widget.checked; + } + } else if (value != widget.initialValue) { + values[ propName ] = value ? value: null; + } } } var that = this; - xhr.put({url: putURL, sync: true, handleAs: "json", + var url = putURL; + if (appendNameToUrl){ + url = url + "/" + encodeURIComponent(values["name"]); + } + xhr.put({url: url , sync: true, handleAs: "json", headers: { "Content-Type": "application/json"}, putData: json.toJson(values), load: function(x) {that.success = true; }, @@ -280,15 +291,24 @@ define(["dojo/_base/xhr", }); form.connectChildren(true); setAttributesDialog.startup(); - setAttributesDialog.on("show", function(){ - var data = geometry.position(dialogContentArea); - var maxHeight = win.getBox().h * 0.6; - if (data.h > maxHeight) - { - dialogContentArea.style.height = maxHeight + "px"; - dialogContentArea.style.overflow= "auto"; - } - }) + var formWidgets = form.getDescendants(); + var aproximateHeight = 0; + for(var i in formWidgets){ + var widget = formWidgets[i]; + var propName = widget.name; + if (propName) { + if ((widget instanceof dijit.form.CheckBox || widget instanceof dijit.form.RadioButton)) { + widget.initialValue = widget.checked; + } else { + widget.initialValue = widget.value; + } + aproximateHeight += 30; + } + } + var viewport = win.getBox(); + var maxHeight = Math.max(Math.floor(viewport.h * 0.6), 100); + dialogContentArea.style.overflow= "auto"; + dialogContentArea.style.height = Math.min(aproximateHeight, maxHeight ) + "px"; setAttributesDialog.show(); }; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js index 04ac80784a..b07b68c835 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js @@ -31,6 +31,7 @@ define(["dojo/_base/xhr", "qpid/management/addAuthenticationProvider", "qpid/management/addVirtualHost", "qpid/management/addPort", + "qpid/management/addKeystore", "dojox/grid/enhanced/plugins/Pagination", "dojox/grid/enhanced/plugins/IndirectSelection", "dijit/layout/AccordionContainer", @@ -41,7 +42,7 @@ define(["dojo/_base/xhr", "dijit/form/CheckBox", "dojo/store/Memory", "dojo/domReady!"], - function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid, registry, addAuthenticationProvider, addVirtualHost, addPort) { + function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid, registry, addAuthenticationProvider, addVirtualHost, addPort, addKeystore) { function Broker(name, parent, controller) { this.name = name; @@ -116,79 +117,6 @@ define(["dojo/_base/xhr", name: "groupFile"}); } }, { - name: "keyStorePath", - createWidget: function(brokerData) { - return new dijit.form.ValidationTextBox({ - required: false, - value: brokerData.keyStorePath, - label: "Path to keystore:", - name: "keyStorePath"}); - } - }, { - name: "keyStorePassword", - requiredFor: "keyStorePath", - createWidget: function(brokerData) { - return new dijit.form.ValidationTextBox({ - required: false, - label: "Keystore password:", - invalidMessage: "Missed keystore password", - name: "keyStorePassword", - placeholder: brokerData["keyStorePassword"] ? brokerData["keyStorePassword"] : "" - }); - } - }, { - name: "keyStoreCertAlias", - createWidget: function(brokerData) { - return new dijit.form.ValidationTextBox({ - required: false, - value: brokerData.keyStoreCertAlias, - label: "Keystore certificate alias:", - name: "keyStoreCertAlias"}); - } - }, { - name: "trustStorePath", - createWidget: function(brokerData) - { - return new dijit.form.ValidationTextBox({ - required: false, - value: brokerData.trustStorePath, - label: "Path to truststore:", - name: "trustStorePath"}); - } - }, { - name: "trustStorePassword", - requiredFor: "trustStorePath", - createWidget: function(brokerData) { - return new dijit.form.ValidationTextBox({ - required: false, - label: "Truststore password:", - invalidMessage: "Missed trustore password", - name: "trustStorePassword", - placeholder: brokerData["trustStorePassword"] ? brokerData["trustStorePassword"] : "" - }); - } - }, { - name: "peerStorePath", - createWidget: function(brokerData) { - return new dijit.form.ValidationTextBox({ - required: false, - value: brokerData.peerStorePath, - label: "Path to peerstore:", - name: "peerStorePath"}); - } - }, { - name: "peerStorePassword", - requiredFor: "peerStorePath", - createWidget: function(brokerData) { - return new dijit.form.ValidationTextBox({ - required: false, - label: "Peerstore password:", - invalidMessage: "Missed peerstore password", - name: "peerStorePassword", - placeholder: brokerData["peerStorePassword"] ? brokerData["peerStorePassword"] : "" - }); - } - }, { name: "statisticsReportingPeriod", createWidget: function(brokerData) { return new dijit.form.ValidationTextBox({ @@ -488,7 +416,10 @@ define(["dojo/_base/xhr", ); var addPortButton = query(".addPort", contentPane.containerNode)[0]; - connect.connect(registry.byNode(addPortButton), "onClick", function(evt){ addPort.show(null, that.brokerUpdater.brokerData.authenticationproviders); }); + connect.connect(registry.byNode(addPortButton), "onClick", function(evt){ + addPort.show(null, that.brokerUpdater.brokerData.authenticationproviders, + that.brokerUpdater.brokerData.keystores, that.brokerUpdater.brokerData.truststores); + }); var deletePort = query(".deletePort", contentPane.containerNode)[0]; connect.connect(registry.byNode(deletePort), "onClick", @@ -512,6 +443,35 @@ define(["dojo/_base/xhr", } ); + var addKeystoreButton = query(".addKeystore", contentPane.containerNode)[0]; + connect.connect(registry.byNode(addKeystoreButton), "onClick", + function(evt){ addKeystore.showKeystoreDialog() }); + + var deleteKeystore = query(".deleteKeystore", contentPane.containerNode)[0]; + connect.connect(registry.byNode(deleteKeystore), "onClick", + function(evt){ + util.deleteGridSelections( + that.brokerUpdater, + that.brokerUpdater.keyStoresGrid.grid, + "rest/keystore", + "Are you sure you want to delete key store"); + } + ); + + var addTruststoreButton = query(".addTruststore", contentPane.containerNode)[0]; + connect.connect(registry.byNode(addTruststoreButton), "onClick", + function(evt){ addKeystore.showTruststoreDialog() }); + + var deleteTruststore = query(".deleteTruststore", contentPane.containerNode)[0]; + connect.connect(registry.byNode(deleteTruststore), "onClick", + function(evt){ + util.deleteGridSelections( + that.brokerUpdater, + that.brokerUpdater.trustStoresGrid.grid, + "rest/truststore", + "Are you sure you want to delete trust store"); + } + ); }}); }; @@ -581,7 +541,7 @@ define(["dojo/_base/xhr", var idx = evt.rowIndex, theItem = this.getItem(idx); var name = obj.dataStore.getValue(theItem,"name"); - addPort.show(name, that.brokerData.authenticationproviders); + addPort.show(name, that.brokerData.authenticationproviders, that.brokerData.keystores, that.brokerData.truststores); }); }, gridProperties, EnhancedGrid); @@ -615,6 +575,44 @@ define(["dojo/_base/xhr", }); }, gridProperties, EnhancedGrid); + that.keyStoresGrid = + new UpdatableStore(that.brokerData.keystores, query(".broker-key-stores")[0], + [ { name: "Name", field: "name", width: "20%"}, + { name: "Path", field: "path", width: "50%"}, + { name: "Type", field: "type", width: "5%"}, + { name: "Key Manager Algorithm", field: "keyManagerFactoryAlgorithm", width: "20%"}, + { name: "Alias", field: "certificateAlias", width: "15%"} + ], function(obj) { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var name = obj.dataStore.getValue(theItem,"name"); + that.controller.show("keystore", name, brokerObj); + }); + }, gridProperties, EnhancedGrid); + + that.trustStoresGrid = + new UpdatableStore(that.brokerData.truststores, query(".broker-trust-stores")[0], + [ { name: "Name", field: "name", width: "20%"}, + { name: "Path", field: "path", width: "50%"}, + { name: "Type", field: "type", width: "5%"}, + { name: "Trust Manager Algorithm", field: "trustManagerFactoryAlgorithm", width: "20%"}, + { name: "Peers only", field: "peersOnly", width: "15%", + formatter: function(val){ + return "<input type='radio' disabled='disabled' "+(val ? "checked='checked'": "")+" />"; + } + } + ], function(obj) { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var name = obj.dataStore.getValue(theItem,"name"); + that.controller.show("truststore", name, brokerObj); + }); + }, gridProperties, EnhancedGrid); + }); xhr.get({url: "rest/logrecords", sync: properties.useSyncGet, handleAs: "json"}) @@ -699,6 +697,15 @@ define(["dojo/_base/xhr", that.portsGrid.update(that.brokerData.ports); that.authenticationProvidersGrid.update(that.brokerData.authenticationproviders); + + if (that.keyStoresGrid) + { + that.keyStoresGrid.update(that.brokerData.keystores); + } + if (that.trustStoresGrid) + { + that.trustStoresGrid.update(that.brokerData.truststores); + } }); @@ -718,7 +725,7 @@ define(["dojo/_base/xhr", dojo.byId("brokerAttribute.operatingSystem").innerHTML = brokerData.operatingSystem; dojo.byId("brokerAttribute.platform").innerHTML = brokerData.platform; dojo.byId("brokerAttribute.productVersion").innerHTML = brokerData.productVersion; - dojo.byId("brokerAttribute.modelVersion").innerHTML = brokerData.managementVersion; + dojo.byId("brokerAttribute.modelVersion").innerHTML = brokerData.modelVersion; dojo.byId("brokerAttribute.storeType").innerHTML = brokerData.storeType; dojo.byId("brokerAttribute.storeVersion").innerHTML = brokerData.storeVersion; dojo.byId("brokerAttribute.storePath").innerHTML = brokerData.storePath; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/KeyStore.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/KeyStore.js new file mode 100644 index 0000000000..9702c6b9f6 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/KeyStore.js @@ -0,0 +1,160 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +define(["dojo/dom", + "dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "dijit/registry", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/formatter", + "qpid/management/addKeystore", + "dojo/domReady!"], + function (dom, xhr, parser, query, connect, registry, properties, updater, util, formatter, addKeystore) { + + function KeyStore(name, parent, controller, objectType) { + this.keyStoreName = name; + this.controller = controller; + this.modelObj = { type: "keystore", name: name }; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + this.url = "rest/keystore/" + encodeURIComponent(name); + this.dialog = addKeystore.showKeystoreDialog; + } + + KeyStore.prototype.getTitle = function() { + return "KeyStore: " + this.keyStoreName; + }; + + KeyStore.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showKeyStore.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.keyStoreUpdater = new KeyStoreUpdater(contentPane.containerNode, that.modelObj, that.controller, that.url); + + updater.add( that.keyStoreUpdater ); + + that.keyStoreUpdater.update(); + + var deleteKeyStoreButton = query(".deleteKeyStoreButton", contentPane.containerNode)[0]; + var node = registry.byNode(deleteKeyStoreButton); + connect.connect(node, "onClick", + function(evt){ + that.deleteKeyStore(); + }); + + var editKeyStoreButton = query(".editKeyStoreButton", contentPane.containerNode)[0]; + var node = registry.byNode(editKeyStoreButton); + connect.connect(node, "onClick", + function(evt){ + that.dialog(that.keyStoreUpdater.keyStoreData) + }); + }}); + }; + + KeyStore.prototype.close = function() { + updater.remove( this.keyStoreUpdater ); + }; + + function KeyStoreUpdater(containerNode, keyStoreObj, controller, url) + { + var that = this; + + function findNode(name) { + return query("." + name + "Value", containerNode)[0]; + } + + function storeNodes(names) + { + for(var i = 0; i < names.length; i++) { + that[names[i]] = findNode(names[i]); + } + } + + storeNodes(["name", + "path", + "type", + "keyManagerFactoryAlgorithm", + "certificateAlias", + "peersOnly" + ]); + + this.query = url; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + that.keyStoreData = data[0]; + that.updateHeader(); + }); + + } + + KeyStoreUpdater.prototype.updateHeader = function() + { + this.name.innerHTML = this.keyStoreData[ "name" ]; + this.path.innerHTML = this.keyStoreData[ "path" ]; + this.type.innerHTML = this.keyStoreData[ "type" ]; + this.keyManagerFactoryAlgorithm.innerHTML = this.keyStoreData[ "keyManagerFactoryAlgorithm" ]; + this.certificateAlias.innerHTML = this.keyStoreData[ "certificateAlias" ] ? this.keyStoreData[ "certificateAlias" ] : ""; + }; + + KeyStoreUpdater.prototype.update = function() + { + + var thisObj = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + thisObj.keyStoreData = data[0]; + thisObj.updateHeader(); + }); + }; + + KeyStore.prototype.deleteKeyStore = function() { + if(confirm("Are you sure you want to delete key store '" +this.keyStoreName+"'?")) { + var query = this.url; + this.success = true + var that = this; + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) { + that.contentPane.onClose() + that.controller.tabContainer.removeChild(that.contentPane); + that.contentPane.destroyRecursive(); + that.close(); + }, + function(error) {that.success = false; that.failureReason = error;}); + if(!this.success ) { + alert("Error:" + this.failureReason); + } + } + } + + return KeyStore; + }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/TrustStore.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/TrustStore.js new file mode 100644 index 0000000000..703ef34ec2 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/TrustStore.js @@ -0,0 +1,160 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +define(["dojo/dom", + "dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "dijit/registry", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/formatter", + "qpid/management/addKeystore", + "dojo/domReady!"], + function (dom, xhr, parser, query, connect, registry, properties, updater, util, formatter, addKeystore) { + + function TrustStore(name, parent, controller) { + this.keyStoreName = name; + this.controller = controller; + this.modelObj = { type: "truststore", name: name }; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + this.url = "rest/truststore/" + encodeURIComponent(name); + this.dialog = addKeystore.showTruststoreDialog; + } + + TrustStore.prototype.getTitle = function() { + return "TrustStore: " + this.keyStoreName; + }; + + TrustStore.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showTrustStore.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.keyStoreUpdater = new KeyStoreUpdater(contentPane.containerNode, that.modelObj, that.controller, that.url); + + updater.add( that.keyStoreUpdater ); + + that.keyStoreUpdater.update(); + + var deleteTrustStoreButton = query(".deleteTrustStoreButton", contentPane.containerNode)[0]; + var node = registry.byNode(deleteTrustStoreButton); + connect.connect(node, "onClick", + function(evt){ + that.deleteKeyStore(); + }); + + var editTrustStoreButton = query(".editTrustStoreButton", contentPane.containerNode)[0]; + var node = registry.byNode(editTrustStoreButton); + connect.connect(node, "onClick", + function(evt){ + that.dialog(that.keyStoreUpdater.keyStoreData) + }); + }}); + }; + + TrustStore.prototype.close = function() { + updater.remove( this.keyStoreUpdater ); + }; + + function KeyStoreUpdater(containerNode, keyStoreObj, controller, url) + { + var that = this; + + function findNode(name) { + return query("." + name + "Value", containerNode)[0]; + } + + function storeNodes(names) + { + for(var i = 0; i < names.length; i++) { + that[names[i]] = findNode(names[i]); + } + } + + storeNodes(["name", + "path", + "type", + "trustManagerFactoryAlgorithm", + "certificateAlias", + "peersOnly" + ]); + + this.query = url; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + that.keyStoreData = data[0]; + that.updateHeader(); + }); + + } + + KeyStoreUpdater.prototype.updateHeader = function() + { + this.name.innerHTML = this.keyStoreData[ "name" ]; + this.path.innerHTML = this.keyStoreData[ "path" ]; + this.type.innerHTML = this.keyStoreData[ "type" ]; + this.trustManagerFactoryAlgorithm.innerHTML = this.keyStoreData[ "trustManagerFactoryAlgorithm" ]; + this.peersOnly.innerHTML = "<input type='checkbox' disabled='disabled' "+(this.keyStoreData[ "peersOnly" ] ? "checked='checked'": "")+" />" ; + }; + + KeyStoreUpdater.prototype.update = function() + { + + var thisObj = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + thisObj.keyStoreData = data[0]; + thisObj.updateHeader(); + }); + }; + + TrustStore.prototype.deleteKeyStore = function() { + if(confirm("Are you sure you want to delete trust store '" +this.keyStoreName+"'?")) { + var query = this.url; + this.success = true + var that = this; + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) { + that.contentPane.onClose() + that.controller.tabContainer.removeChild(that.contentPane); + that.contentPane.destroyRecursive(); + that.close(); + }, + function(error) {that.success = false; that.failureReason = error;}); + if(!this.success ) { + alert("Error:" + this.failureReason); + } + } + } + + return TrustStore; + }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js index decc7fa0b3..d2891c7d3b 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addAuthenticationProvider.js @@ -207,7 +207,7 @@ define(["dojo/_base/xhr", var layout = new dojox.layout.TableContainer( { id: providerType + "FieldSet", cols: 1, - "labelWidth": "300", + "labelWidth": "200", showLabels: true, orientation: "horiz" }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addKeystore.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addKeystore.js new file mode 100644 index 0000000000..4fdcffb7f1 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addKeystore.js @@ -0,0 +1,164 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +define(["dojo/_base/lang", + "dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + 'dojo/_base/json', + "qpid/common/util", + "dojo/store/Memory", + "dojox/validate/us", + "dojox/validate/web", + "dijit/Dialog", + "dijit/form/CheckBox", + "dijit/form/Textarea", + "dijit/form/ComboBox", + "dijit/form/TextBox", + "dijit/form/ValidationTextBox", + "dijit/form/Button", + "dijit/form/Form", + "dijit/TitlePane", + "dojox/layout/TableContainer", + "dojo/domReady!"], + function (lang, xhr, dom, construct, registry, parser, array, event, json, util) { + + var addKeystore = { }; + + addKeystore.createWidgetFactories = function(isKeystore) + { + var fields = [{ + name: "name", + createWidget: function(keystore) { + return new dijit.form.ValidationTextBox({ + required: true, + value: keystore.name, + disabled: keystore.name ? true : false, + label: "Name:", + regexp: "^[\x20-\x2e\x30-\x7F]{1,255}$", + name: "name"}); + } + }, { + name: "path", + createWidget: function(keystore) { + return new dijit.form.ValidationTextBox({ + required: true, + value: keystore.path, + label: "Path to keystore:", + name: "path"}); + } + }, { + name: "password", + requiredFor: "path", + createWidget: function(keystore) { + return new dijit.form.ValidationTextBox({ + required: false, + label: "Keystore password:", + invalidMessage: "Missed keystore password", + name: "password", + placeHolder: keystore["password"] ? keystore["password"] : "" + }); + } + }]; + if (!isKeystore) + { + fields.push({ + name: "peersOnly", + createWidget: function(keystore) { + return new dijit.form.CheckBox({ + required: false, + checked: keystore && keystore.peersOnly, + label: "Peers only:", + name: "peersOnly"}); + } + }); + } + fields.push({ + name: "Options", + createWidget: function(keystore) { + var optionalFieldContainer = new dojox.layout.TableContainer({ + cols: 1, + "labelWidth": "290", + showLabels: true, + orientation: "horiz", + customClass: "formLabel" + }); + if (isKeystore) + { + optionalFieldContainer.addChild(new dijit.form.ValidationTextBox({ + required: false, + value: keystore.certificateAlias, + label: "Keystore certificate alias:", + name: "certificateAlias"})); + optionalFieldContainer.addChild( new dijit.form.ValidationTextBox({ + required: false, + value: keystore.keyManagerFactoryAlgorithm, + label: "Key manager factory algorithm:", + placeHolder: "Use default", + name: "keyManagerFactoryAlgorithm"})); + } + else + { + optionalFieldContainer.addChild( new dijit.form.ValidationTextBox({ + required: false, + value: keystore.trustManagerFactoryAlgorithm, + label: "Trust manager factory algorithm:", + placeHolder: "Use default", + name: "trustManagerFactoryAlgorithm"})); + } + optionalFieldContainer.addChild(new dijit.form.ValidationTextBox({ + required: false, + value: keystore.type, + label: "Key store type:", + placeHolder: "Use default", + name: "type"})); + var panel = new dijit.TitlePane({title: "Optional Attributes", content: optionalFieldContainer.domNode, open: false}); + return panel; + } + }); + return fields; + } + + addKeystore.showKeystoreDialog = function(keystore) { + var keystoreAttributeWidgetFactories = addKeystore.createWidgetFactories(true); + + util.showSetAttributesDialog( + keystoreAttributeWidgetFactories, + keystore ? keystore : {}, + "rest/keystore" + (keystore ? "/" + encodeURIComponent(keystore.name) : ""), + keystore ? "Edit keystore - " + keystore.name : "Add keystore", + keystore ? false : true); + }; + + addKeystore.showTruststoreDialog = function(truststore) { + var truststoreAttributeWidgetFactories = addKeystore.createWidgetFactories(false); + util.showSetAttributesDialog( + truststoreAttributeWidgetFactories, + truststore ? truststore : {}, + "rest/truststore" + (truststore ? "/" + encodeURIComponent(truststore.name) : ""), + truststore ? "Edit truststore - " + truststore.name : "Add truststore", + truststore ? false : true); + }; + return addKeystore; + });
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js index 0c1a188cbf..c60ad5bb79 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js @@ -28,6 +28,7 @@ define(["dojo/_base/xhr", "dojo/_base/event", 'dojo/_base/json', "dojo/store/Memory", + "dojo/data/ObjectStore", "dijit/form/FilteringSelect", "dojo/dom-style", "dojo/_base/lang", @@ -50,9 +51,10 @@ define(["dojo/_base/xhr", "dijit/form/Select", "dijit/form/NumberSpinner", /* basic dojox classes */ - "dojox/form/BusyButton", + "dojox/grid/EnhancedGrid", + "dojox/grid/enhanced/plugins/IndirectSelection", "dojo/domReady!"], - function (xhr, dom, construct, win, registry, parser, array, event, json, Memory, FilteringSelect, domStyle, lang) { + function (xhr, dom, construct, win, registry, parser, array, event, json, Memory, ObjectStore, FilteringSelect, domStyle, lang) { var addPort = {}; @@ -110,33 +112,77 @@ define(["dojo/_base/xhr", var type = dijit.byId("formAddPort.type").value; if (type == "AMQP") { + var transportWidget = registry.byId("formAddPort.transports"); var needClientAuth = dijit.byId("formAddPort.needClientAuth"); var wantClientAuth = dijit.byId("formAddPort.wantClientAuth"); - newPort.needClientAuth = needClientAuth.disabled ? false : needClientAuth.checked; - newPort.wantClientAuth = wantClientAuth.disabled ? false : wantClientAuth.checked + var trustStoreWidget = dijit.byId("formAddPort.trustStores"); + + var initialTransport = transportWidget.initialValue; + var currentTransport = transportWidget.value; + if (currentTransport == "SSL") + { + newPort.needClientAuth = needClientAuth.checked; + newPort.wantClientAuth = wantClientAuth.checked + + var items = trustStoreWidget.selection.getSelected(); + var trustStores = []; + if(items.length > 0){ + for(var i in items) + { + var item = items[i]; + trustStores.push(trustStoreWidget.store.getValue(item, "name")); + } + newPort.trustStores = trustStores; + } + else if (trustStoreWidget.initialValue && trustStoreWidget.initialValue.length > 0) + { + newPort.trustStores = null; + } + } + else if (initialTransport && currentTransport != initialTransport) + { + newPort.needClientAuth = false; + newPort.wantClientAuth = false; + newPort.trustStores = null; + } } + return newPort; }; - var toggleCertificateWidgets = function toggleCertificateWidgets(protocolType, transportType) + var toggleSslWidgets = function toggleSslWidgets(protocolType, transportType) { - var clientAuthPanel = registry.byId("formAddPort:fieldsClientAuth"); - var display = clientAuthPanel.domNode.style.display; + var clientAuthPanel = dojo.byId("formAddPort:fieldsClientAuth"); + var display = clientAuthPanel.style.display; if (transportType == "SSL" && protocolType == "AMQP") { - clientAuthPanel.domNode.style.display = "block"; + clientAuthPanel.style.display = "block"; registry.byId("formAddPort.needClientAuth").set("disabled", false); registry.byId("formAddPort.wantClientAuth").set("disabled", false); } else { - clientAuthPanel.domNode.style.display = "none"; + clientAuthPanel.style.display = "none"; registry.byId("formAddPort.needClientAuth").set("disabled", true); registry.byId("formAddPort.wantClientAuth").set("disabled", true); } - if (clientAuthPanel.domNode.style.display != display) + + var transportSSLPanel = registry.byId("formAddPort:fieldsTransportSSL"); + var transportSSLPanelDisplay = transportSSLPanel.domNode.style.display; + if (transportType == "SSL") + { + transportSSLPanel.domNode.style.display = "block"; + registry.byId("formAddPort.keyStore").set("disabled", false); + } + else { - clientAuthPanel.resize(); + transportSSLPanel.domNode.style.display = "none"; + registry.byId("formAddPort.keyStore").set("disabled", true); + } + + if (transportSSLPanel.domNode.style.display != transportSSLPanelDisplay) + { + transportSSLPanel.resize(); } }; @@ -155,7 +201,7 @@ define(["dojo/_base/xhr", registry.byId("formAddPort.transports").on("change", function(newValue){ var protocolType = registry.byId("formAddPort.type").value; - toggleCertificateWidgets(protocolType, newValue); + toggleSslWidgets(protocolType, newValue); }); registry.byId("formAddPort.type").on("change", function(newValue) { @@ -166,8 +212,9 @@ define(["dojo/_base/xhr", registry.byId("formAddPort:fields" + option.value).domNode.style.display = "none"; }); - registry.byId("formAddPort.needClientAuth").set("enabled", ("AMQP" == newValue)); - registry.byId("formAddPort.wantClientAuth").set("enabled", ("AMQP" == newValue)); + var isAMQP = ("AMQP" == newValue); + registry.byId("formAddPort.needClientAuth").set("enabled", isAMQP); + registry.byId("formAddPort.wantClientAuth").set("enabled", isAMQP); registry.byId("formAddPort:fields" + newValue).domNode.style.display = "block"; var defaultsAMQPProtocols = registry.byId("formAddPort.protocolsDefault"); @@ -175,19 +222,58 @@ define(["dojo/_base/xhr", var protocolsWidget = registry.byId("formAddPort.protocols" + newValue); if (protocolsWidget) { - protocolsWidget.set("disabled", ("AMQP" == newValue && defaultsAMQPProtocols.checked)); + protocolsWidget.set("disabled", (isAMQP && defaultsAMQPProtocols.checked)); + } + var transportWidget = registry.byId("formAddPort.transports"); + + var disabled = (newValue == "JMX" && registry.byId("formAddPort.protocolsJMX").value == "RMI"); + if (disabled && transportWidget.value != "TCP") + { + transportWidget.set("value", "TCP"); } - var transport = registry.byId("formAddPort.transports").value; - toggleCertificateWidgets(newValue, transport); + else + { + toggleSslWidgets(newValue, transportWidget.value); + } + transportWidget.set("disabled", disabled); + }); + theForm = registry.byId("formAddPort"); + var containers = ["formAddPort:fields", "formAddPort:fieldsTransportSSL", "formAddPort:fieldsAMQP", + "formAddPort:fieldsJMX", "formAddPort:fieldsHTTP", "formAddPort:transport", "formAddPort:fieldsClientAuth2"]; + var labelWidthValue = "200"; + for(var i = 0; i < containers.length; i++) + { + var containerId = containers[i]; + var fields = new dojox.layout.TableContainer( { + cols: 1, + labelWidth: labelWidthValue, + showLabels: true, + orientation: "horiz", + customClass: "formLabel" + }, dom.byId(containerId)); + fields.startup(); + } + + registry.byId("formAddPort.protocolsJMX").on("change", function(newValue){ + var transportWidget = registry.byId("formAddPort.transports"); + transportWidget.set("value", "TCP"); + transportWidget.set("disabled", newValue == "RMI"); + }); + theForm.on("submit", function(e) { event.stop(e); if(theForm.validate()){ var newPort = convertToPort(theForm.getValues()); + if ((newPort.needClientAuth || newPort.wantClientAuth) && (!newPort.hasOwnProperty("trustStores") || newPort.trustStores.length==0)) + { + alert("A trustore must be selected when requesting client certificates."); + return false; + } var that = this; xhr.put({url: "rest/port/"+encodeURIComponent(newPort.name), sync: true, handleAs: "json", @@ -216,52 +302,8 @@ define(["dojo/_base/xhr", }); }}); - addPort.show = function(portName, providers) { + addPort.show = function(portName, providers, keystores, truststores) { - if (!addPort.fields) - { - var labelWidthValue = "300"; - addPort.fields = new dojox.layout.TableContainer( { - cols: 1, - labelWidth: labelWidthValue, - showLabels: true, - orientation: "horiz", - customClass: "formLabel" - }, dom.byId("formAddPort:fields")); - addPort.fields.startup(); - addPort.fieldsClientAuth = new dojox.layout.TableContainer( { - cols: 1, - labelWidth: labelWidthValue, - showLabels: true, - orientation: "horiz", - customClass: "formLabel" - }, dom.byId("formAddPort:fieldsClientAuth")); - addPort.fieldsClientAuth.startup(); - addPort.fieldsAMQP = new dojox.layout.TableContainer( { - cols: 1, - labelWidth: labelWidthValue, - showLabels: true, - orientation: "horiz", - customClass: "formLabel" - }, dom.byId("formAddPort:fieldsAMQP")); - addPort.fieldsAMQP.startup(); - addPort.fieldsJMX = new dojox.layout.TableContainer( { - cols: 1, - labelWidth: labelWidthValue, - showLabels: true, - orientation: "horiz", - customClass: "formLabel" - }, dom.byId("formAddPort:fieldsJMX")); - addPort.fieldsJMX.startup(); - addPort.fieldsHTTP = new dojox.layout.TableContainer( { - cols: 1, - labelWidth: labelWidthValue, - showLabels: true, - orientation: "horiz", - customClass: "formLabel" - }, dom.byId("formAddPort:fieldsHTTP")); - addPort.fieldsHTTP.startup(); - } registry.byId("formAddPort").reset(); dojo.byId("formAddPort.id").value = ""; @@ -278,6 +320,36 @@ define(["dojo/_base/xhr", providerWidget.startup(); } + var keystoreWidget = registry.byId("formAddPort.keyStore"); + if (keystores) + { + var data = []; + for (var i=0; i< keystores.length; i++) + { + data.push( {id: keystores[i].name, name: keystores[i].name} ); + } + var keystoresStore = new Memory({ data: data }); + keystoreWidget.set("store", keystoresStore); + keystoreWidget.startup(); + } + + var truststoreWidget = registry.byId("formAddPort.trustStores"); + if (truststores) + { + var layout = [[{name: "Name", field: "name", width: "100%"}, + {name: "Peers only", field: "peersOnly", width: "80px", + formatter: function(val){ + return "<input type='radio' disabled='disabled' "+(val?"checked='checked'": "")+" />" + } + }]]; + + var mem = new Memory({ data: truststores, idProperty: "id"}); + truststoreWidget.set("store", new ObjectStore({objectStore: mem})); + truststoreWidget.set("structure", layout); + truststoreWidget.rowSelectCell.toggleAllSelection(false); + truststoreWidget.startup(); + } + if (portName) { xhr.get({ @@ -291,6 +363,26 @@ define(["dojo/_base/xhr", nameField.set("disabled", true); dom.byId("formAddPort.id").value=port.id; providerWidget.set("value", port.authenticationProvider ? port.authenticationProvider : ""); + keystoreWidget.set("value", port.keyStore ? port.keyStore : ""); + if (port.trustStores) + { + var items = truststoreWidget.store.objectStore.data; + for (var j=0; j< items.length; j++) + { + var selected = false; + for (var i=0; i< port.trustStores.length; i++) + { + var trustStore = port.trustStores[i]; + if (items[j].name == trustStore) + { + selected = true; + break; + } + } + truststoreWidget.selection.setSelected(j,selected); + truststoreWidget.initialValue = port.trustStores; + } + } var transportWidget = registry.byId("formAddPort.transports"); transportWidget.set("value", port.transports ? port.transports[0] : ""); registry.byId("formAddPort.port").set("value", port.port); @@ -343,16 +435,26 @@ define(["dojo/_base/xhr", registry.byId("formAddPort:fields" + typeWidget.value).domNode.style.display = "block"; typeWidget.set("disabled", true); - toggleCertificateWidgets(typeWidget.value, transportWidget.value); + toggleSslWidgets(typeWidget.value, transportWidget.value); + + keystoreWidget.initialValue = port.keyStore; + truststoreWidget.initialValue = port.trustStores; + transportWidget.initialValue = transportWidget.value; + providerWidget.initialValue = providerWidget.value; + registry.byId("addPort").show(); }); } else { var typeWidget = registry.byId("formAddPort.type"); - typeWidget.set("disabled", false); + if (typeWidget.get("disabled")) + { + typeWidget.set("disabled", false); + } typeWidget.set("value", "AMQP"); - registry.byId("formAddPort.name").set("disabled", false); + var name = registry.byId("formAddPort.name"); + name.set("disabled", false); registry.byId("addPort").show(); } }; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js index 5d3a666760..c4114739c0 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js @@ -29,15 +29,17 @@ define(["dojo/dom", "qpid/management/AuthenticationProvider", "qpid/management/GroupProvider", "qpid/management/group/Group", + "qpid/management/KeyStore", + "qpid/management/TrustStore", "dojo/ready", "dojo/domReady!"], - function (dom, registry, ContentPane, Broker, VirtualHost, Exchange, Queue, Connection, AuthProvider, GroupProvider, Group, ready) { + function (dom, registry, ContentPane, Broker, VirtualHost, Exchange, Queue, Connection, AuthProvider, GroupProvider, Group, KeyStore, TrustStore, ready) { var controller = {}; var constructors = { broker: Broker, virtualhost: VirtualHost, exchange: Exchange, queue: Queue, connection: Connection, authenticationprovider: AuthProvider, groupprovider: GroupProvider, - group: Group }; + group: Group, keystore: KeyStore, truststore: TrustStore }; var tabDiv = dom.byId("managedViews"); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js index 59356cfce1..f96fc13a03 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js @@ -277,8 +277,11 @@ define(["dojo/_base/xhr", controller.show("groupprovider", details.groupprovider, {broker: {type:"broker", name:""}}); } else if (details.type == 'group') { controller.show("group", details.group, { type: "groupprovider", name: details.groupprovider, parent: {broker: {type:"broker", name:""}}}); + } else if (details.type == 'keystore') { + controller.show("keystore", details.keystore, {broker: {type:"broker", name:""}}); + } else if (details.type == 'truststore') { + controller.show("truststore", details.truststore, {broker: {type:"broker", name:""}}); } - }; TreeViewModel.prototype.update = function () { diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html index 887ca4e736..9e267f0d5d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html @@ -69,20 +69,6 @@ <div class="formLabel-labelCell" style="float:left; width: 250px;">Group file location:</div> <div id="brokerAttribute.groupFile" style="float:left;"></div> </div> - <div id="brokerAttribute.keyStorePath.container" style="display: none; clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to keystore:</div> - <div id="brokerAttribute.keyStorePath" style="float:left;"></div><br/> - <div class="formLabel-labelCell" style="float:left; width: 250px;">Keystore certificate alias:</div> - <div id="brokerAttribute.keyStoreCertAlias" style="float:left;"></div> - </div> - <div id="brokerAttribute.trustStorePath.container" style="display: none; clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to truststore:</div> - <div id="brokerAttribute.trustStorePath" style="float:left;"></div> - </div> - <div id="brokerAttribute.peerStorePath.container" style="display: none; clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to peerstore:</div> - <div id="brokerAttribute.peerStorePath" style="float:left;"></div> - </div> <div id="brokerAttribute.statisticsReportingPeriod.container" style="display: none; clear:both"> <div class="formLabel-labelCell" style="float:left; width: 250px;">Statistics reporting period:</div> <div id="brokerAttribute.statisticsReportingPeriod" style="float:left;"></div> @@ -191,6 +177,18 @@ <button data-dojo-type="dijit.form.Button" class="deleteAuthenticationProvider">Delete Provider</button> </div> <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Key stores'"> + <div class="broker-key-stores"></div> + <button data-dojo-type="dijit.form.Button" class="addKeystore">Add Key Store</button> + <button data-dojo-type="dijit.form.Button" class="deleteKeystore">Delete Key Store</button> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Trust stores'"> + <div class="broker-trust-stores"></div> + <button data-dojo-type="dijit.form.Button" class="addTruststore">Add Trust Store</button> + <button data-dojo-type="dijit.form.Button" class="deleteTruststore">Delete Trust Store</button> + </div> + <br/> <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Log File', open: false"> <div class="broker-logfile"></div> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showKeyStore.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showKeyStore.html new file mode 100644 index 0000000000..5caee836d3 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showKeyStore.html @@ -0,0 +1,47 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you 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. + - + --> +<div class="keystore"> + <div class="keyStoreContainer"> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Name:</div> + <div class="nameValue" style="float:left;"></div><br/> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Type:</div> + <div class="typeValue" style="float:left;"></div><br/> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Key Manager Factory Algorithm:</div> + <div class="keyManagerFactoryAlgorithmValue" style="float:left;"></div><br/> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path:</div> + <div class="pathValue" style="float:left;"></div><br/> + + <div class="certificateAlias"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Certificate alias:</div> + <div class="certificateAliasValue" style="float:left;"></div><br> + </div> + + </div> + <br/> + <div class="dijitDialogPaneActionBar"> + <button data-dojo-type="dijit.form.Button" class="editKeyStoreButton" type="button">Edit</button> + <button data-dojo-type="dijit.form.Button" class="deleteKeyStoreButton" type="button">Delete</button> + </div> +</div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showTrustStore.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showTrustStore.html new file mode 100644 index 0000000000..6f9146fdfe --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showTrustStore.html @@ -0,0 +1,47 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you 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. + - + --> +<div class="truststore"> + <div class="trustStoreContainer"> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Name:</div> + <div class="nameValue" style="float:left;"></div><br/> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Type:</div> + <div class="typeValue" style="float:left;"></div><br/> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Trust Manager Factory Algorithm:</div> + <div class="trustManagerFactoryAlgorithmValue" style="float:left;"></div><br/> + + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path:</div> + <div class="pathValue" style="float:left;"></div><br/> + + <div class="peersOnly"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Peer store:</div> + <div class="peersOnlyValue" style="float:left;"></div> + </div> + + </div> + <br/> + <div class="dijitDialogPaneActionBar"> + <button data-dojo-type="dijit.form.Button" class="editTrustStoreButton" type="button">Edit</button> + <button data-dojo-type="dijit.form.Button" class="deleteTrustStoreButton" type="button">Delete</button> + </div> +</div> diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java index 93f7df8c85..a1356028f0 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java @@ -26,10 +26,12 @@ import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Transport; import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator; +import org.apache.qpid.ssl.SSLContextFactory; import javax.management.JMException; import javax.management.MBeanServer; @@ -39,6 +41,7 @@ import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXServiceURL; import javax.management.remote.MBeanServerForwarder; import javax.management.remote.rmi.RMIConnectorServer; +import javax.net.ssl.SSLContext; import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; import java.io.File; @@ -57,6 +60,7 @@ import java.rmi.registry.Registry; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.UnicastRemoteObject; +import java.security.GeneralSecurityException; import java.util.HashMap; /** @@ -122,16 +126,32 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry if (connectorSslEnabled) { - String keyStorePath = System.getProperty("javax.net.ssl.keyStore"); - String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword"); + KeyStore keyStore = _connectorPort.getKeyStore(); - validateKeyStoreProperties(keyStorePath, keyStorePassword); + String keyStorePath = (String) keyStore.getAttribute(KeyStore.PATH); + String keyStorePassword = keyStore.getPassword(); + String keyStoreType = (String) keyStore.getAttribute(KeyStore.TYPE); + String keyManagerFactoryAlgorithm = (String) keyStore.getAttribute(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM); + + SSLContext sslContext; + try + { + sslContext = SSLContextFactory.buildServerContext(keyStorePath, keyStorePassword, keyStoreType, keyManagerFactoryAlgorithm); + } + catch (GeneralSecurityException e) + { + throw new RuntimeException("Unable to create SSLContext for key or trust store", e); + } + catch (IOException e) + { + throw new RuntimeException("Unable to create SSLContext - unable to load key/trust store", e); + } CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(keyStorePath)); //create the SSL RMI socket factories csf = new SslRMIClientSocketFactory(); - ssf = new SslRMIServerSocketFactory(); + ssf = new QpidSslRMIServerSocketFactory(sslContext); } else { @@ -262,31 +282,6 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry return rmiRegistry; } - private void validateKeyStoreProperties(String keyStorePath, String keyStorePassword) throws FileNotFoundException - { - if (keyStorePath == null) - { - throw new IllegalConfigurationException("JVM system property 'javax.net.ssl.keyStore' is not set, " - + "unable to start requested SSL protected JMX connector"); - } - if (keyStorePassword == null) - { - throw new IllegalConfigurationException( "JVM system property 'javax.net.ssl.keyStorePassword' is not set, " - + "unable to start requested SSL protected JMX connector"); - } - - File ksf = new File(keyStorePath); - if (!ksf.exists()) - { - throw new FileNotFoundException("Cannot find SSL keystore file for JMX management: " + ksf); - } - if (!ksf.canRead()) - { - throw new FileNotFoundException("Cannot read SSL keystore file for JMX management: " - + ksf + ". Check permissions."); - } - } - @Override public void registerObject(ManagedObject managedObject) throws JMException { diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/QpidSslRMIServerSocketFactory.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/QpidSslRMIServerSocketFactory.java new file mode 100644 index 0000000000..115a96da81 --- /dev/null +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/QpidSslRMIServerSocketFactory.java @@ -0,0 +1,85 @@ +package org.apache.qpid.server.jmx; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +public class QpidSslRMIServerSocketFactory extends SslRMIServerSocketFactory +{ + private final SSLContext _sslContext; + + /** + * SslRMIServerSocketFactory which creates the ServerSocket using the + * supplied SSLContext rather than the system default context normally + * used by the superclass, allowing us to use a configuration-specified + * key store. + * + * @param sslContext previously created sslContext using the desired key store. + * @throws NullPointerException if the provided {@link SSLContext} is null. + */ + public QpidSslRMIServerSocketFactory(SSLContext sslContext) throws NullPointerException + { + super(); + + if(sslContext == null) + { + throw new NullPointerException("The provided SSLContext must not be null"); + } + + _sslContext = sslContext; + + //TODO: settings + implementation for SSL client auth, updating equals and hashCode appropriately. + } + + @Override + public ServerSocket createServerSocket(int port) throws IOException + { + final SSLSocketFactory factory = _sslContext.getSocketFactory(); + + return new ServerSocket(port) + { + public Socket accept() throws IOException + { + Socket socket = super.accept(); + + SSLSocket sslSocket = + (SSLSocket) factory.createSocket(socket, + socket.getInetAddress().getHostName(), + socket.getPort(), + true); + sslSocket.setUseClientMode(false); + + return sslSocket; + } + }; + } + + /** + * One QpidSslRMIServerSocketFactory is equal to + * another if their (non-null) SSLContext are equal. + */ + @Override + public boolean equals(Object object) + { + if (!(object instanceof QpidSslRMIServerSocketFactory)) + { + return false; + } + + QpidSslRMIServerSocketFactory that = (QpidSslRMIServerSocketFactory) object; + + return _sslContext.equals(that._sslContext); + } + + @Override + public int hashCode() + { + return _sslContext.hashCode(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java index 0d7be75a0b..4fc0a37c3e 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java @@ -1,23 +1,28 @@ package org.apache.qpid.server.configuration.startup; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.qpid.server.configuration.ConfigurationEntry; import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.configuration.updater.TaskExecutor; import org.apache.qpid.server.logging.LogRecorder; import org.apache.qpid.server.logging.RootMessageLogger; import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.adapter.AuthenticationProviderFactory; import org.apache.qpid.server.model.adapter.BrokerAdapter; import org.apache.qpid.server.model.adapter.PortFactory; -import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; -import org.apache.qpid.server.configuration.updater.TaskExecutor; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; @@ -52,31 +57,62 @@ public class BrokerRecoverer implements ConfiguredObjectRecoverer<Broker> _logRecorder, _rootMessageLogger, _authenticationProviderFactory, _portFactory, _taskExecutor, entry.getStore()); broker.addChangeListener(storeChangeListener); - Map<String, Collection<ConfigurationEntry>> childEntries = entry.getChildren(); - for (String type : childEntries.keySet()) + + //Recover the SSL keystores / truststores first, then others that depend on them + Map<String, Collection<ConfigurationEntry>> childEntries = new HashMap<String, Collection<ConfigurationEntry>>(entry.getChildren()); + Map<String, Collection<ConfigurationEntry>> sslChildEntries = new HashMap<String, Collection<ConfigurationEntry>>(childEntries); + List<String> types = new ArrayList<String>(childEntries.keySet()); + + for(String type : types) { - ConfiguredObjectRecoverer<?> recoverer = recovererProvider.getRecoverer(type); - if (recoverer == null) + if(KeyStore.class.getSimpleName().equals(type) || TrustStore.class.getSimpleName().equals(type)) { - throw new IllegalConfigurationException("Cannot recover entry for the type '" + type + "' from broker"); + childEntries.remove(type); } - Collection<ConfigurationEntry> entries = childEntries.get(type); - for (ConfigurationEntry childEntry : entries) + else { - ConfiguredObject object = recoverer.create(recovererProvider, childEntry, broker); - if (object == null) - { - throw new IllegalConfigurationException("Cannot create configured object for the entry " + childEntry); - } - broker.recoverChild(object); - object.addChangeListener(storeChangeListener); + sslChildEntries.remove(type); } } + + for (String type : sslChildEntries.keySet()) + { + recoverType(recovererProvider, storeChangeListener, broker, sslChildEntries, type); + } + for (String type : childEntries.keySet()) + { + recoverType(recovererProvider, storeChangeListener, broker, childEntries, type); + } + wireUpConfiguredObjects(broker, entry.getAttributes()); return broker; } + private void recoverType(RecovererProvider recovererProvider, + StoreConfigurationChangeListener storeChangeListener, + BrokerAdapter broker, + Map<String, Collection<ConfigurationEntry>> childEntries, + String type) + { + ConfiguredObjectRecoverer<?> recoverer = recovererProvider.getRecoverer(type); + if (recoverer == null) + { + throw new IllegalConfigurationException("Cannot recover entry for the type '" + type + "' from broker"); + } + Collection<ConfigurationEntry> entries = childEntries.get(type); + for (ConfigurationEntry childEntry : entries) + { + ConfiguredObject object = recoverer.create(recovererProvider, childEntry, broker); + if (object == null) + { + throw new IllegalConfigurationException("Cannot create configured object for the entry " + childEntry); + } + broker.recoverChild(object); + object.addChangeListener(storeChangeListener); + } + } + private void wireUpConfiguredObjects(BrokerAdapter broker, Map<String, Object> brokerAttributes) { AuthenticationProvider defaultAuthenticationProvider = null; diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java index ad0e68cee6..0e230bd552 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java @@ -88,18 +88,6 @@ public interface Broker extends ConfiguredObject String ACL_FILE = "aclFile"; /* - * A temporary attributes to set the broker default key/trust stores. - * TODO: Remove them after adding a full support to configure KeyStore/TrustStore via management layers. - */ - String KEY_STORE_PATH = "keyStorePath"; - String KEY_STORE_PASSWORD = "keyStorePassword"; - String KEY_STORE_CERT_ALIAS = "keyStoreCertAlias"; - String TRUST_STORE_PATH = "trustStorePath"; - String TRUST_STORE_PASSWORD = "trustStorePassword"; - String PEER_STORE_PATH = "peerStorePath"; - String PEER_STORE_PASSWORD = "peerStorePassword"; - - /* * A temporary attributes to set the broker group file. * TODO: Remove them after adding a full support to configure authorization providers via management layers. */ @@ -148,16 +136,8 @@ public interface Broker extends ConfiguredObject VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE, VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN, - - ACL_FILE, - KEY_STORE_PATH, - KEY_STORE_PASSWORD, - KEY_STORE_CERT_ALIAS, - TRUST_STORE_PATH, - TRUST_STORE_PASSWORD, - PEER_STORE_PATH, - PEER_STORE_PASSWORD, - GROUP_FILE + GROUP_FILE, + ACL_FILE )); //children @@ -194,6 +174,10 @@ public interface Broker extends ConfiguredObject VirtualHost findVirtualHostByName(String name); + KeyStore findKeyStoreByName(String name); + + TrustStore findTrustStoreByName(String name); + /** * Get the SubjectCreator for the given socket address. * TODO: move the authentication related functionality into host aliases and AuthenticationProviders @@ -211,10 +195,6 @@ public interface Broker extends ConfiguredObject */ VirtualHostRegistry getVirtualHostRegistry(); - KeyStore getDefaultKeyStore(); - - TrustStore getDefaultTrustStore(); - TaskExecutor getTaskExecutor(); boolean isManagementMode(); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/KeyStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/KeyStore.java index 959714656b..74a7469ffb 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/KeyStore.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/KeyStore.java @@ -24,10 +24,23 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -public interface KeyStore extends TrustStore +public interface KeyStore extends ConfiguredObject { + String ID = "id"; + String NAME = "name"; + String DURABLE = "durable"; + String LIFETIME_POLICY = "lifetimePolicy"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String CREATED = "created"; + String UPDATED = "updated"; + String DESCRIPTION = "description"; + String PATH = "path"; + String PASSWORD = "password"; + String TYPE = "type"; String CERTIFICATE_ALIAS = "certificateAlias"; + String KEY_MANAGER_FACTORY_ALGORITHM = "keyManagerFactoryAlgorithm"; public static final Collection<String> AVAILABLE_ATTRIBUTES = Collections.unmodifiableList( @@ -44,8 +57,11 @@ public interface KeyStore extends TrustStore PATH, PASSWORD, TYPE, - KEY_MANAGER_FACTORY_ALGORITHM, - CERTIFICATE_ALIAS + CERTIFICATE_ALIAS, + KEY_MANAGER_FACTORY_ALGORITHM )); + public String getPassword(); + + public void setPassword(String password); } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Port.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Port.java index 33ba34767d..7ff5f04ee7 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Port.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Port.java @@ -45,6 +45,8 @@ public interface Port extends ConfiguredObject String NEED_CLIENT_AUTH = "needClientAuth"; String WANT_CLIENT_AUTH = "wantClientAuth"; String AUTHENTICATION_PROVIDER = "authenticationProvider"; + String KEY_STORE = "keyStore"; + String TRUST_STORES = "trustStores"; // Attributes public static final Collection<String> AVAILABLE_ATTRIBUTES = @@ -67,7 +69,9 @@ public interface Port extends ConfiguredObject RECEIVE_BUFFER_SIZE, NEED_CLIENT_AUTH, WANT_CLIENT_AUTH, - AUTHENTICATION_PROVIDER + AUTHENTICATION_PROVIDER, + KEY_STORE, + TRUST_STORES )); @@ -75,6 +79,10 @@ public interface Port extends ConfiguredObject int getPort(); + KeyStore getKeyStore(); + + Collection<TrustStore> getTrustStores(); + Collection<Transport> getTransports(); void addTransport(Transport transport) throws IllegalStateException, diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java index 53498ab431..c686e7bd50 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java @@ -38,9 +38,9 @@ public interface TrustStore extends ConfiguredObject String PATH = "path"; String PASSWORD = "password"; - String PEERS_ONLY = "peersOnly"; String TYPE = "type"; - String KEY_MANAGER_FACTORY_ALGORITHM = "keyManagerFactoryAlgorithm"; + String PEERS_ONLY = "peersOnly"; + String TRUST_MANAGER_FACTORY_ALGORITHM = "trustManagerFactoryAlgorithm"; public static final Collection<String> AVAILABLE_ATTRIBUTES = Collections.unmodifiableList( @@ -56,9 +56,9 @@ public interface TrustStore extends ConfiguredObject DESCRIPTION, PATH, PASSWORD, - PEERS_ONLY, TYPE, - KEY_MANAGER_FACTORY_ALGORITHM + PEERS_ONLY, + TRUST_MANAGER_FACTORY_ALGORITHM )); public String getPassword(); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java index 20929a337a..51fd3a5c78 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java @@ -63,6 +63,7 @@ abstract class AbstractAdapter implements ConfiguredObject { if (attributes.containsKey(name)) { + //TODO: dont put nulls _attributes.put(name, attributes.get(name)); } } @@ -254,6 +255,7 @@ abstract class AbstractAdapter implements ConfiguredObject if((currentValue == null && expected == null) || (currentValue != null && currentValue.equals(expected))) { + //TODO: dont put nulls _attributes.put(name, desired); return true; } @@ -397,4 +399,51 @@ abstract class AbstractAdapter implements ConfiguredObject { return _defaultAttributes; } + + /** + * Returns a map of effective attribute values that would result + * if applying the supplied changes. Does not apply the changes. + */ + protected Map<String, Object> generateEffectiveAttributes(Map<String,Object> changedValues) + { + //Build a new set of effective attributes that would be + //the result of applying the attribute changes, so we + //can validate the configuration that would result + + Map<String, Object> defaultValues = getDefaultAttributes(); + Map<String, Object> existingActualValues = getActualAttributes(); + + //create a new merged map, starting with the defaults + Map<String, Object> merged = new HashMap<String, Object>(defaultValues); + + for(String name : getAttributeNames()) + { + if(changedValues.containsKey(name)) + { + Object changedValue = changedValues.get(name); + if(changedValue != null) + { + //use the new non-null value for the merged values + merged.put(name, changedValue); + } + else + { + //we just use the default (if there was one) since the changed + //value is null and effectively clears any existing actual value + } + } + else if(existingActualValues.get(name) != null) + { + //Use existing non-null actual value for the merge + merged.put(name, existingActualValues.get(name)); + } + else + { + //There was neither a change or an existing non-null actual + //value, so just use the default value (if there was one). + } + } + + return merged; + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java index 80196c395e..707cf8076d 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java @@ -37,20 +37,21 @@ import org.apache.qpid.server.util.MapValueConverter; public abstract class AbstractKeyStoreAdapter extends AbstractAdapter { + public static final String DUMMY_PASSWORD_MASK = "********"; + public static final String DEFAULT_KEYSTORE_TYPE = java.security.KeyStore.getDefaultType(); + private String _name; private String _password; - protected AbstractKeyStoreAdapter(UUID id, Broker broker, Map<String, Object> attributes) + protected AbstractKeyStoreAdapter(UUID id, Broker broker, Map<String, Object> defaults, + Map<String, Object> attributes) { - super(id, broker.getTaskExecutor()); + super(id, defaults, attributes, broker.getTaskExecutor()); addParent(Broker.class, broker); + _name = MapValueConverter.getStringAttribute(TrustStore.NAME, attributes); _password = MapValueConverter.getStringAttribute(TrustStore.PASSWORD, attributes); - setMandatoryAttribute(TrustStore.PATH, attributes); - setOptionalAttribute(TrustStore.PEERS_ONLY, attributes); - setOptionalAttribute(TrustStore.TYPE, attributes); - setOptionalAttribute(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, attributes); - setOptionalAttribute(TrustStore.DESCRIPTION, attributes); + MapValueConverter.assertMandatoryAttribute(KeyStore.PATH, attributes); } @Override @@ -163,15 +164,16 @@ public abstract class AbstractKeyStoreAdapter extends AbstractAdapter } else if(KeyStore.PASSWORD.equals(name)) { - return null; // for security reasons we don't expose the password + // For security reasons we don't expose the password + if (getPassword() != null) + { + return DUMMY_PASSWORD_MASK; + } + + return null; } - return super.getAttribute(name); - } - @Override - protected boolean setState(State currentState, State desiredState) - { - return false; + return super.getAttribute(name); } public String getPassword() @@ -183,25 +185,4 @@ public abstract class AbstractKeyStoreAdapter extends AbstractAdapter { _password = password; } - - private void setMandatoryAttribute(String name, Map<String, Object> attributeValues) - { - changeAttribute(name, null, MapValueConverter.getStringAttribute(name, attributeValues)); - } - - private void setOptionalAttribute(String name, Map<String, Object> attributeValues) - { - Object attrValue = attributeValues.get(name); - if (attrValue != null) - { - if (attrValue instanceof Boolean) - { - changeAttribute(name, null, MapValueConverter.getBooleanAttribute(name, attributeValues)); - } - else - { - changeAttribute(name, null, MapValueConverter.getStringAttribute(name, attributeValues)); - } - } - } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java index e7057f89d3..71f5397d2b 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java @@ -133,15 +133,11 @@ public class AmqpPortAdapter extends PortAdapter private SSLContext createSslContext() { - KeyStore keyStore = _broker.getDefaultKeyStore(); - if (keyStore == null) - { - throw new IllegalConfigurationException("SSL was requested on AMQP port '" - + this.getName() + "' but no key store defined"); - } + KeyStore keyStore = getKeyStore(); - Collection<TrustStore> trustStores = _broker.getTrustStores(); - if (((Boolean)getAttribute(NEED_CLIENT_AUTH) || (Boolean)getAttribute(WANT_CLIENT_AUTH)) && trustStores.isEmpty()) + Collection<TrustStore> trustStores = getTrustStores(); + boolean needClientCert = (Boolean)getAttribute(NEED_CLIENT_AUTH) || (Boolean)getAttribute(WANT_CLIENT_AUTH); + if (needClientCert && trustStores.isEmpty()) { throw new IllegalConfigurationException("Client certificate authentication is enabled on AMQP port '" + this.getName() + "' but no trust store defined"); @@ -165,7 +161,7 @@ public class AmqpPortAdapter extends PortAdapter trustStore.getPassword(), (String)trustStore.getAttribute(TrustStore.TYPE), (Boolean) trustStore.getAttribute(TrustStore.PEERS_ONLY), - (String)trustStore.getAttribute(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM))); + (String)trustStore.getAttribute(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM))); } sslContext = SSLContextFactory.buildClientContext(trstWrappers, keystorePath, keystorePassword, keystoreType, diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java index e968d91e79..ae30f70877 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -24,7 +24,6 @@ import java.lang.reflect.Type; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.AccessControlException; -import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -32,13 +31,12 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; -import javax.net.ssl.KeyManagerFactory; -import java.security.cert.Certificate; - import org.apache.log4j.Logger; import org.apache.qpid.common.QpidProperties; import org.apache.qpid.server.configuration.ConfigurationEntryStore; import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.store.ManagementModeStoreHandler; +import org.apache.qpid.server.configuration.updater.TaskExecutor; import org.apache.qpid.server.logging.LogRecorder; import org.apache.qpid.server.logging.RootMessageLogger; import org.apache.qpid.server.logging.actors.BrokerActor; @@ -58,22 +56,18 @@ import org.apache.qpid.server.model.Statistics; import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.model.VirtualHost; -import org.apache.qpid.server.configuration.store.ManagementModeStoreHandler; -import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.group.FileGroupManager; import org.apache.qpid.server.security.group.GroupManager; -import org.apache.qpid.server.security.SecurityManager; -import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.store.MessageStoreCreator; import org.apache.qpid.server.util.MapValueConverter; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; -import org.apache.qpid.transport.network.security.ssl.SSLUtil; public class BrokerAdapter extends AbstractAdapter implements Broker, ConfigurationChangeListener { - private static final Logger LOGGER = Logger.getLogger(BrokerAdapter.class); @SuppressWarnings("serial") @@ -100,13 +94,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat put(DEFAULT_VIRTUAL_HOST, String.class); put(DEFAULT_AUTHENTICATION_PROVIDER, String.class); - put(KEY_STORE_PATH, String.class); - put(KEY_STORE_PASSWORD, String.class); - put(KEY_STORE_CERT_ALIAS, String.class); - put(TRUST_STORE_PATH, String.class); - put(TRUST_STORE_PASSWORD, String.class); - put(PEER_STORE_PATH, String.class); - put(PEER_STORE_PASSWORD, String.class); put(GROUP_FILE, String.class); put(VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, Long.class); put(VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, Long.class); @@ -133,12 +120,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat public static final long DEFAULT_STORE_TRANSACTION_IDLE_TIMEOUT_WARN = 0l; public static final long DEFAULT_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE = 0l; public static final long DEFAULT_STORE_TRANSACTION_OPEN_TIMEOUT_WARN = 0l; - private static final String DEFAULT_KEY_STORE_NAME = "defaultKeyStore"; - private static final String DEFAULT_TRUST_STORE_NAME = "defaultTrustStore"; private static final String DEFAULT_GROUP_PROVIDER_NAME = "defaultGroupProvider"; - private static final String DEFAULT_PEER_STORE_NAME = "defaultPeerStore"; - - private static final String DUMMY_PASSWORD_MASK = "********"; @SuppressWarnings("serial") private static final Map<String, Object> DEFAULTS = Collections.unmodifiableMap(new HashMap<String, Object>(){{ @@ -182,16 +164,14 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private final Map<UUID, AuthenticationProvider> _authenticationProviders = new HashMap<UUID, AuthenticationProvider>(); private final Map<String, GroupProvider> _groupProviders = new HashMap<String, GroupProvider>(); private final Map<UUID, ConfiguredObject> _plugins = new HashMap<UUID, ConfiguredObject>(); - private final Map<UUID, KeyStore> _keyStores = new HashMap<UUID, KeyStore>(); - private final Map<UUID, TrustStore> _trustStores = new HashMap<UUID, TrustStore>(); + private final Map<String, KeyStore> _keyStores = new HashMap<String, KeyStore>(); + private final Map<String, TrustStore> _trustStores = new HashMap<String, TrustStore>(); private final AuthenticationProviderFactory _authenticationProviderFactory; private AuthenticationProvider _defaultAuthenticationProvider; private final PortFactory _portFactory; private final SecurityManager _securityManager; - private final UUID _defaultKeyStoreId; - private final UUID _defaultTrustStoreId; private final Collection<String> _supportedStoreTypes; private final ConfigurationEntryStore _brokerStore; @@ -212,8 +192,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat _portFactory = portFactory; _securityManager = new SecurityManager((String)getAttribute(ACL_FILE)); addChangeListener(_securityManager); - _defaultKeyStoreId = UUIDGenerator.generateBrokerChildUUID(KeyStore.class.getSimpleName(), DEFAULT_KEY_STORE_NAME); - _defaultTrustStoreId = UUIDGenerator.generateBrokerChildUUID(TrustStore.class.getSimpleName(), DEFAULT_TRUST_STORE_NAME); createBrokerChildrenFromAttributes(); _supportedStoreTypes = new MessageStoreCreator().getStoreTypes(); _brokerStore = brokerStore; @@ -226,9 +204,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private void createBrokerChildrenFromAttributes() { createGroupProvider(); - createKeyStore(); - createTrustStore(); - createPeerStore(); } private void createGroupProvider() @@ -248,80 +223,12 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat } } - private void createKeyStore() - { - Map<String, Object> actualAttributes = getActualAttributes(); - String keyStorePath = (String) getAttribute(KEY_STORE_PATH); - if (keyStorePath != null) - { - Map<String, Object> keyStoreAttributes = new HashMap<String, Object>(); - keyStoreAttributes.put(KeyStore.NAME, DEFAULT_KEY_STORE_NAME); - keyStoreAttributes.put(KeyStore.PATH, keyStorePath); - keyStoreAttributes.put(KeyStore.PASSWORD, (String) actualAttributes.get(KEY_STORE_PASSWORD)); - keyStoreAttributes.put(KeyStore.TYPE, java.security.KeyStore.getDefaultType()); - keyStoreAttributes.put(KeyStore.CERTIFICATE_ALIAS, getAttribute(KEY_STORE_CERT_ALIAS)); - keyStoreAttributes.put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); - KeyStoreAdapter keyStoreAdapter = new KeyStoreAdapter(_defaultKeyStoreId, this, keyStoreAttributes); - _keyStores.put(keyStoreAdapter.getId(), keyStoreAdapter); - } - else - { - _keyStores.remove(_defaultKeyStoreId); - } - } - - private void createTrustStore() - { - Map<String, Object> actualAttributes = getActualAttributes(); - String trustStorePath = (String) getAttribute(TRUST_STORE_PATH); - if (trustStorePath != null) - { - Map<String, Object> trsustStoreAttributes = new HashMap<String, Object>(); - trsustStoreAttributes.put(TrustStore.NAME, DEFAULT_TRUST_STORE_NAME); - trsustStoreAttributes.put(TrustStore.PATH, trustStorePath); - trsustStoreAttributes.put(TrustStore.PEERS_ONLY, Boolean.FALSE); - trsustStoreAttributes.put(TrustStore.PASSWORD, (String) actualAttributes.get(TRUST_STORE_PASSWORD)); - trsustStoreAttributes.put(TrustStore.TYPE, java.security.KeyStore.getDefaultType()); - trsustStoreAttributes.put(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); - TrustStoreAdapter trustStore = new TrustStoreAdapter(_defaultTrustStoreId, this, trsustStoreAttributes); - _trustStores.put(trustStore.getId(), trustStore); - } - else - { - _trustStores.remove(_defaultTrustStoreId); - } - } - - private void createPeerStore() - { - Map<String, Object> actualAttributes = getActualAttributes(); - String peerStorePath = (String) getAttribute(PEER_STORE_PATH); - UUID peerStoreId = UUIDGenerator.generateBrokerChildUUID(TrustStore.class.getSimpleName(), DEFAULT_PEER_STORE_NAME); - if (peerStorePath != null) - { - Map<String, Object> peerStoreAttributes = new HashMap<String, Object>(); - peerStoreAttributes.put(TrustStore.NAME, peerStoreId.toString()); - peerStoreAttributes.put(TrustStore.PATH, peerStorePath); - peerStoreAttributes.put(TrustStore.PEERS_ONLY, Boolean.TRUE); - peerStoreAttributes.put(TrustStore.PASSWORD, (String) actualAttributes.get(PEER_STORE_PASSWORD)); - peerStoreAttributes.put(TrustStore.TYPE, java.security.KeyStore.getDefaultType()); - peerStoreAttributes.put(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); - TrustStoreAdapter trustStore = new TrustStoreAdapter(peerStoreId, this, peerStoreAttributes); - _trustStores.put(trustStore.getId(), trustStore); - } - else - { - _trustStores.remove(peerStoreId); - } - } - public Collection<VirtualHost> getVirtualHosts() { synchronized(_vhostAdapters) { return new ArrayList<VirtualHost>(_vhostAdapters.values()); } - } public Collection<Port> getPorts() @@ -354,6 +261,22 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat return null; } + public KeyStore findKeyStoreByName(String keyStoreName) + { + synchronized(_keyStores) + { + return _keyStores.get(keyStoreName); + } + } + + public TrustStore findTrustStoreByName(String trustStoreName) + { + synchronized(_trustStores) + { + return _trustStores.get(trustStoreName); + } + } + @Override public AuthenticationProvider getDefaultAuthenticationProvider() { @@ -396,14 +319,14 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat // permission has already been granted to create the virtual host // disable further access check on other operations, e.g. create exchange - _securityManager.setAccessChecksDisabled(true); + SecurityManager.setAccessChecksDisabled(true); try { virtualHostAdapter.setDesiredState(State.INITIALISING, State.ACTIVE); } finally { - _securityManager.setAccessChecksDisabled(false); + SecurityManager.setAccessChecksDisabled(false); } return virtualHostAdapter; } @@ -526,6 +449,14 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat { return (C) createAuthenticationProvider(attributes); } + else if(childClass == KeyStore.class) + { + return (C) createKeyStore(attributes); + } + else if(childClass == TrustStore.class) + { + return (C) createTrustStore(attributes); + } else { throw new IllegalArgumentException("Cannot create child of class " + childClass.getSimpleName()); @@ -618,40 +549,76 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat throw new UnsupportedOperationException("Not implemented yet!"); } + private KeyStore createKeyStore(Map<String, Object> attributes) + { + KeyStore keyStore = new KeyStoreAdapter(UUIDGenerator.generateRandomUUID(), this, attributes); + addKeyStore(keyStore); + + return keyStore; + } + + private TrustStore createTrustStore(Map<String, Object> attributes) + { + TrustStore trustStore = new TrustStoreAdapter(UUIDGenerator.generateRandomUUID(), this, attributes); + addTrustStore(trustStore); + + return trustStore; + } + private void addKeyStore(KeyStore keyStore) { synchronized (_keyStores) { - if(_keyStores.containsKey(keyStore.getId())) + if(_keyStores.containsKey(keyStore.getName())) { - throw new IllegalConfigurationException("Cannot add KeyStore because one with id " + keyStore.getId() + " already exists"); + throw new IllegalConfigurationException("Can't add KeyStore because one with name " + keyStore.getName() + " already exists"); } - _keyStores.put(keyStore.getId(), keyStore); + _keyStores.put(keyStore.getName(), keyStore); } keyStore.addChangeListener(this); } private boolean deleteKeyStore(KeyStore object) { - throw new UnsupportedOperationException("Not implemented yet!"); + synchronized(_keyStores) + { + String name = object.getName(); + KeyStore removedKeyStore = _keyStores.remove(name); + if(removedKeyStore != null) + { + removedKeyStore.removeChangeListener(this); + } + + return removedKeyStore != null; + } } private void addTrustStore(TrustStore trustStore) { synchronized (_trustStores) { - if(_trustStores.containsKey(trustStore.getId())) + if(_trustStores.containsKey(trustStore.getName())) { - throw new IllegalConfigurationException("Cannot add TrustStore because one with id " + trustStore.getId() + " already exists"); + throw new IllegalConfigurationException("Can't add TrustStore because one with name " + trustStore.getName() + " already exists"); } - _trustStores.put(trustStore.getId(), trustStore); + _trustStores.put(trustStore.getName(), trustStore); } trustStore.addChangeListener(this); } private boolean deleteTrustStore(TrustStore object) { - throw new UnsupportedOperationException("Not implemented yet!"); + synchronized(_trustStores) + { + String name = object.getName(); + TrustStore removedTrustStore = _trustStores.remove(name); + if(removedTrustStore != null) + { + removedTrustStore.removeChangeListener(this); + } + + return removedTrustStore != null; + } } @Override @@ -726,14 +693,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat { return _authenticationProviderFactory.getSupportedAuthenticationProviders(); } - else if (KEY_STORE_PASSWORD.equals(name) || TRUST_STORE_PASSWORD.equals(name) || PEER_STORE_PASSWORD.equals(name)) - { - if (getActualAttributes().get(name) != null) - { - return DUMMY_PASSWORD_MASK; - } - return null; - } else if (MODEL_VERSION.equals(name)) { return Model.MODEL_MAJOR_VERSION + "." + Model.MODEL_MINOR_VERSION; @@ -885,6 +844,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat { childDeleted = deleteTrustStore((TrustStore)object); } + if(childDeleted) { childRemoved(object); @@ -1034,18 +994,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat } @Override - public KeyStore getDefaultKeyStore() - { - return _keyStores.get(_defaultKeyStoreId); - } - - @Override - public TrustStore getDefaultTrustStore() - { - return _trustStores.get(_defaultTrustStoreId); - } - - @Override public TaskExecutor getTaskExecutor() { return super.getTaskExecutor(); @@ -1058,9 +1006,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat Map<String, Object> convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); validateAttributes(convertedAttributes); - boolean keyStoreChanged = false; - boolean trustStoreChanged = false; - boolean peerStoreChanged = false; Collection<String> names = AVAILABLE_ATTRIBUTES; for (String name : names) { @@ -1081,34 +1026,11 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat _defaultAuthenticationProvider = findAuthenticationProviderByName((String)desired); } } - else if (KEY_STORE_PATH.equals(name) || KEY_STORE_PASSWORD.equals(name) || KEY_STORE_CERT_ALIAS.equals(name)) - { - keyStoreChanged = true; - } - else if (TRUST_STORE_PATH.equals(name) || TRUST_STORE_PASSWORD.equals(name)) - { - trustStoreChanged = true; - } - else if (PEER_STORE_PATH.equals(name) || PEER_STORE_PASSWORD.equals(name)) - { - peerStoreChanged = true; - } + attributeSet(name, expected, desired); } } } - if (keyStoreChanged) - { - createKeyStore(); - } - if (trustStoreChanged) - { - createTrustStore(); - } - if (peerStoreChanged) - { - createPeerStore(); - } } private void validateAttributes(Map<String, Object> convertedAttributes) @@ -1125,9 +1047,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat // create a group manager to validate the groups specified in file new FileGroupManager(groupFile); } - validateKeyStoreAttributes(convertedAttributes, "key store", KEY_STORE_PATH, KEY_STORE_PASSWORD, KEY_STORE_CERT_ALIAS); - validateKeyStoreAttributes(convertedAttributes, "trust store", TRUST_STORE_PATH, TRUST_STORE_PASSWORD, null); - validateKeyStoreAttributes(convertedAttributes, "peer store", PEER_STORE_PATH, PEER_STORE_PASSWORD, null); + String defaultAuthenticationProvider = (String) convertedAttributes.get(DEFAULT_AUTHENTICATION_PROVIDER); if (defaultAuthenticationProvider != null) { @@ -1176,59 +1096,6 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat } } - private void validateKeyStoreAttributes(Map<String, Object> convertedAttributes, String type, String pathAttribute, - String passwordAttribute, String aliasAttribute) - { - String keyStoreFile = (String) convertedAttributes.get(pathAttribute); - String password = (String) convertedAttributes.get(passwordAttribute); - String alias = aliasAttribute!= null? (String) convertedAttributes.get(aliasAttribute) : null; - if (keyStoreFile != null || password != null || alias != null) - { - if (keyStoreFile == null) - { - keyStoreFile = (String) getActualAttributes().get(pathAttribute); - } - if (password == null) - { - password = (String) getActualAttributes().get(passwordAttribute); - } - java.security.KeyStore keyStore = null; - try - { - keyStore = SSLUtil.getInitializedKeyStore(keyStoreFile, password, java.security.KeyStore.getDefaultType()); - } - catch (Exception e) - { - throw new IllegalConfigurationException("Cannot instantiate " + type + " at " + keyStoreFile, e); - } - if (aliasAttribute != null) - { - if (alias == null) - { - alias = (String) getActualAttributes().get(aliasAttribute); - } - if (alias != null) - { - Certificate cert = null; - try - { - cert = keyStore.getCertificate(alias); - } - catch (KeyStoreException e) - { - // key store should be initialized above - throw new RuntimeException("Key store has not been initialized", e); - } - if (cert == null) - { - throw new IllegalConfigurationException("Cannot find a certificate with alias " + alias + "in " + type - + " : " + keyStoreFile); - } - } - } - } - } - @Override protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException { diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java index 113d895e62..4d4d3bb31d 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java @@ -20,23 +20,63 @@ */ package org.apache.qpid.server.model.adapter; +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.UUID; +import javax.net.ssl.KeyManagerFactory; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.IntegrityViolationException; import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; public class KeyStoreAdapter extends AbstractKeyStoreAdapter implements KeyStore { + @SuppressWarnings("serial") + public static final Map<String, Type> ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap<String, Type>(){{ + put(NAME, String.class); + put(PATH, String.class); + put(PASSWORD, String.class); + put(TYPE, String.class); + put(CERTIFICATE_ALIAS, String.class); + put(KEY_MANAGER_FACTORY_ALGORITHM, String.class); + }}); + + @SuppressWarnings("serial") + public static final Map<String, Object> DEFAULTS = Collections.unmodifiableMap(new HashMap<String, Object>(){{ + put(KeyStore.TYPE, DEFAULT_KEYSTORE_TYPE); + put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); + }}); + + private Broker _broker; public KeyStoreAdapter(UUID id, Broker broker, Map<String, Object> attributes) { - super(id, broker, attributes); - if (attributes.get(CERTIFICATE_ALIAS) != null) - { - changeAttribute(CERTIFICATE_ALIAS, null, attributes.get(CERTIFICATE_ALIAS)); - } + super(id, broker, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)); + _broker = broker; + + String keyStorePath = (String)getAttribute(KeyStore.PATH); + String keyStorePassword = getPassword(); + String keyStoreType = (String)getAttribute(KeyStore.TYPE); + String keyManagerFactoryAlgorithm = (String)getAttribute(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM); + String certAlias = (String)getAttribute(KeyStore.CERTIFICATE_ALIAS); + + validateKeyStoreAttributes(keyStoreType, keyStorePath, keyStorePassword, + certAlias, keyManagerFactoryAlgorithm); } @Override @@ -45,4 +85,129 @@ public class KeyStoreAdapter extends AbstractKeyStoreAdapter implements KeyStore return AVAILABLE_ATTRIBUTES; } + @Override + protected boolean setState(State currentState, State desiredState) + { + if(desiredState == State.DELETED) + { + // verify that it is not in use + String storeName = getName(); + + Collection<Port> ports = new ArrayList<Port>(_broker.getPorts()); + for (Port port : ports) + { + if (storeName.equals(port.getAttribute(Port.KEY_STORE))) + { + throw new IntegrityViolationException("Key store '" + storeName + "' can't be deleted as it is in use by a port:" + port.getName()); + } + } + + return true; + } + + return false; + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of key store is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + authoriseSetAttribute(); + } + + @Override + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + authoriseSetAttribute(); + } + + private void authoriseSetAttribute() + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting key store attributes is denied"); + } + } + + @Override + protected void changeAttributes(Map<String, Object> attributes) + { + Map<String, Object> changedValues = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + if(changedValues.containsKey(KeyStore.NAME)) + { + String newName = (String) changedValues.get(KeyStore.NAME); + if(!getName().equals(newName)) + { + throw new IllegalConfigurationException("Changing the key store name is not allowed"); + } + } + + Map<String, Object> merged = generateEffectiveAttributes(changedValues); + + String keyStorePath = (String)merged.get(KeyStore.PATH); + String keyStorePassword = (String) merged.get(KeyStore.PASSWORD); + String keyStoreType = (String)merged.get(KeyStore.TYPE); + String keyManagerFactoryAlgorithm = (String)merged.get(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM); + String certAlias = (String)merged.get(KeyStore.CERTIFICATE_ALIAS); + + validateKeyStoreAttributes(keyStoreType, keyStorePath, keyStorePassword, + certAlias, keyManagerFactoryAlgorithm); + + super.changeAttributes(changedValues); + } + + private void validateKeyStoreAttributes(String type, String keyStorePath, + String keyStorePassword, String alias, + String keyManagerFactoryAlgorithm) + { + java.security.KeyStore keyStore = null; + try + { + keyStore = SSLUtil.getInitializedKeyStore(keyStorePath, keyStorePassword, type); + } + catch (Exception e) + { + throw new IllegalConfigurationException("Cannot instantiate key store at " + keyStorePath, e); + } + + if (alias != null) + { + Certificate cert = null; + try + { + cert = keyStore.getCertificate(alias); + } + catch (KeyStoreException e) + { + // key store should be initialized above + throw new RuntimeException("Key store has not been initialized", e); + } + if (cert == null) + { + throw new IllegalConfigurationException("Cannot find a certificate with alias " + alias + + "in key store : " + keyStorePath); + } + } + + try + { + KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalConfigurationException("Unknown keyManagerFactoryAlgorithm: " + + keyManagerFactoryAlgorithm); + } + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java index 4250de17a7..0c1249d20e 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java @@ -37,12 +37,14 @@ import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.LifetimePolicy; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.Statistics; import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.VirtualHostAlias; import org.apache.qpid.server.security.access.Operation; @@ -58,6 +60,8 @@ public class PortAdapter extends AbstractAdapter implements Port put(NAME, String.class); put(PROTOCOLS, new ParameterizedTypeImpl(Set.class, Protocol.class)); put(TRANSPORTS, new ParameterizedTypeImpl(Set.class, Transport.class)); + put(TRUST_STORES, new ParameterizedTypeImpl(Set.class, String.class)); + put(KEY_STORE, String.class); put(PORT, Integer.class); put(TCP_NO_DELAY, Boolean.class); put(RECEIVE_BUFFER_SIZE, Integer.class); @@ -373,16 +377,40 @@ public class PortAdapter extends AbstractAdapter implements Port boolean requiresCertificate = (needClientCertificate != null && needClientCertificate.booleanValue()) || (wantClientCertificate != null && wantClientCertificate.booleanValue()); - if (transports != null && transports.contains(Transport.SSL)) + String keyStoreName = (String) merged.get(KEY_STORE); + boolean hasKeyStore = keyStoreName != null; + if(keyStoreName != null) { - if (_broker.getKeyStores().isEmpty()) + if (_broker.findKeyStoreByName(keyStoreName) == null) { - throw new IllegalConfigurationException("Can't create port which requires SSL as the broker has no keystore configured."); + throw new IllegalConfigurationException("Can't find key store with name '" + keyStoreName + "' for port " + getName()); + } + } + + Set<String> trustStoreNames = (Set<String>) merged.get(TRUST_STORES); + boolean hasTrustStore = trustStoreNames != null && !trustStoreNames.isEmpty(); + if(hasTrustStore) + { + for (String trustStoreName : trustStoreNames) + { + if (_broker.findTrustStoreByName(trustStoreName) == null) + { + throw new IllegalConfigurationException("Cannot find trust store with name '" + trustStoreName + "'"); + } + } + } + + boolean usesSsl = transports != null && transports.contains(Transport.SSL); + if (usesSsl) + { + if (keyStoreName == null) + { + throw new IllegalConfigurationException("Can't create port which requires SSL but has no key store configured."); } - if (_broker.getTrustStores().isEmpty() && requiresCertificate) + if (!hasTrustStore && requiresCertificate) { - throw new IllegalConfigurationException("Can't create port which requests SSL client certificates as the broker has no trust/peer stores configured."); + throw new IllegalConfigurationException("Can't create port which requests SSL client certificates but has no trust store configured."); } } else @@ -393,9 +421,14 @@ public class PortAdapter extends AbstractAdapter implements Port } } - if (protocols != null && protocols.contains(Protocol.HTTPS) && _broker.getKeyStores().isEmpty()) + if (protocols != null && protocols.contains(Protocol.HTTPS) && !hasKeyStore) { - throw new IllegalConfigurationException("Can't create port which requires SSL as the broker has no keystore configured."); + throw new IllegalConfigurationException("Can't create port which requires SSL but has no key store configured."); + } + + if (protocols != null && protocols.contains(Protocol.RMI) && usesSsl) + { + throw new IllegalConfigurationException("Can't create RMI Registry port which requires SSL."); } String authenticationProviderName = (String)merged.get(AUTHENTICATION_PROVIDER); @@ -450,4 +483,42 @@ public class PortAdapter extends AbstractAdapter implements Port throw new AccessControlException("Setting of port attributes is denied"); } } + + @Override + public KeyStore getKeyStore() + { + String keyStoreName = (String)getAttribute(Port.KEY_STORE); + KeyStore keyStore = _broker.findKeyStoreByName(keyStoreName); + + if (keyStoreName != null && keyStore == null) + { + throw new IllegalConfigurationException("Can't find key store with name '" + keyStoreName + "' for port " + getName()); + } + + return keyStore; + } + + @Override + public Collection<TrustStore> getTrustStores() + { + Set<String> trustStoreNames = (Set<String>) getAttribute(TRUST_STORES); + boolean hasTrustStoreName = trustStoreNames != null && !trustStoreNames.isEmpty(); + + final Collection<TrustStore> trustStores = new ArrayList<TrustStore>(); + if(hasTrustStoreName) + { + for (String name : trustStoreNames) + { + TrustStore trustStore = _broker.findTrustStoreByName(name); + if (trustStore == null) + { + throw new IllegalConfigurationException("Can't find trust store with name '" + name + "' for port " + getName()); + } + + trustStores.add(trustStore); + } + } + + return trustStores; + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java index ffbd24997a..2efe189d73 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java @@ -106,9 +106,9 @@ public class PortFactory port = new AmqpPortAdapter(id, broker, attributes, defaults, broker.getTaskExecutor()); boolean useClientAuth = (Boolean) port.getAttribute(Port.NEED_CLIENT_AUTH) || (Boolean) port.getAttribute(Port.WANT_CLIENT_AUTH); - if(useClientAuth && broker.getTrustStores().isEmpty()) + if(useClientAuth && port.getTrustStores().isEmpty()) { - throw new IllegalConfigurationException("Can't create port which requests SSL client certificates as the broker has no trust/peer stores configured."); + throw new IllegalConfigurationException("Can't create port which requests SSL client certificates but has no trust stores configured."); } if(useClientAuth && !port.getTransports().contains(Transport.SSL)) @@ -142,13 +142,19 @@ public class PortFactory defaults.put(Port.NAME, portValue + "-" + protocol.name()); port = new PortAdapter(id, broker, attributes, defaults, broker.getTaskExecutor()); + + boolean rmiPort = port.getProtocols().contains(Protocol.RMI); + if (rmiPort && port.getTransports().contains(Transport.SSL)) + { + throw new IllegalConfigurationException("Can't create RMI registry port which requires SSL"); + } } if(port.getTransports().contains(Transport.SSL) || port.getProtocols().contains(Protocol.HTTPS)) { - if(broker.getKeyStores().isEmpty()) + if(port.getKeyStore() == null) { - throw new IllegalConfigurationException("Can't create port which requires SSL as the broker has no keystore configured."); + throw new IllegalConfigurationException("Can't create port which requires SSL but has no key store configured."); } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java index bdffe605ec..06089e43c6 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java @@ -20,18 +20,61 @@ */ package org.apache.qpid.server.model.adapter; +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.UUID; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.IntegrityViolationException; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; public class TrustStoreAdapter extends AbstractKeyStoreAdapter implements TrustStore { + @SuppressWarnings("serial") + public static final Map<String, Type> ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap<String, Type>(){{ + put(NAME, String.class); + put(PATH, String.class); + put(PASSWORD, String.class); + put(TYPE, String.class); + put(PEERS_ONLY, Boolean.class); + put(TRUST_MANAGER_FACTORY_ALGORITHM, String.class); + }}); + + @SuppressWarnings("serial") + public static final Map<String, Object> DEFAULTS = Collections.unmodifiableMap(new HashMap<String, Object>(){{ + put(TrustStore.TYPE, DEFAULT_KEYSTORE_TYPE); + put(TrustStore.PEERS_ONLY, Boolean.FALSE); + put(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM, TrustManagerFactory.getDefaultAlgorithm()); + }}); + + private Broker _broker; + public TrustStoreAdapter(UUID id, Broker broker, Map<String, Object> attributes) { - super(id, broker, attributes); + super(id, broker, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)); + _broker = broker; + + String trustStorePath = (String) getAttribute(TrustStore.PATH); + String trustStorePassword = getPassword(); + String trustStoreType = (String) getAttribute(TrustStore.TYPE); + String trustManagerFactoryAlgorithm = (String) getAttribute(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM); + + validateTrustStoreAttributes(trustStoreType, trustStorePath, + trustStorePassword, trustManagerFactoryAlgorithm); } @Override @@ -40,4 +83,110 @@ public class TrustStoreAdapter extends AbstractKeyStoreAdapter implements TrustS return AVAILABLE_ATTRIBUTES; } + @Override + protected boolean setState(State currentState, State desiredState) + { + if(desiredState == State.DELETED) + { + // verify that it is not in use + String storeName = getName(); + + Collection<Port> ports = new ArrayList<Port>(_broker.getPorts()); + for (Port port : ports) + { + Collection<TrustStore> trustStores = port.getTrustStores(); + for(TrustStore store : trustStores) + { + if (storeName.equals(store.getAttribute(TrustStore.NAME))) + { + throw new IntegrityViolationException("Trust store '" + storeName + "' can't be deleted as it is in use by a port: " + port.getName()); + } + } + } + + return true; + } + + return false; + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), TrustStore.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of key store is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + authoriseSetAttribute(); + } + + @Override + protected void authoriseSetAttributes(Map<String, Object> attributes) throws AccessControlException + { + authoriseSetAttribute(); + } + + private void authoriseSetAttribute() + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), TrustStore.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting key store attributes is denied"); + } + } + + @Override + protected void changeAttributes(Map<String, Object> attributes) + { + Map<String, Object> changedValues = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + if(changedValues.containsKey(TrustStore.NAME)) + { + String newName = (String) changedValues.get(TrustStore.NAME); + if(!getName().equals(newName)) + { + throw new IllegalConfigurationException("Changing the trust store name is not allowed"); + } + } + + Map<String, Object> merged = generateEffectiveAttributes(changedValues); + + String trustStorePath = (String)merged.get(TrustStore.PATH); + String trustStorePassword = (String) merged.get(TrustStore.PASSWORD); + String trustStoreType = (String)merged.get(TrustStore.TYPE); + String trustManagerFactoryAlgorithm = (String)merged.get(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM); + + validateTrustStoreAttributes(trustStoreType, trustStorePath, + trustStorePassword, trustManagerFactoryAlgorithm); + + super.changeAttributes(changedValues); + } + + private void validateTrustStoreAttributes(String type, String trustStorePath, + String password, String trustManagerFactoryAlgorithm) + { + try + { + SSLUtil.getInitializedKeyStore(trustStorePath, password, type); + } + catch (Exception e) + { + throw new IllegalConfigurationException("Cannot instantiate trust store at " + trustStorePath, e); + } + + try + { + TrustManagerFactory.getInstance(trustManagerFactoryAlgorithm); + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalConfigurationException("Unknown trustManagerFactoryAlgorithm: " + trustManagerFactoryAlgorithm); + } + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/MapValueConverter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/MapValueConverter.java index 8c57d04348..16e717a9c7 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/MapValueConverter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/MapValueConverter.java @@ -62,7 +62,7 @@ public class MapValueConverter return getStringAttribute(name, attributes, null); } - private static void assertMandatoryAttribute(String name, Map<String, Object> attributes) + public static void assertMandatoryAttribute(String name, Map<String, Object> attributes) { if (!attributes.containsKey(name)) { @@ -326,6 +326,10 @@ public class MapValueConverter public static <T> Set<T> toSet(Object rawValue, Class<T> setItemClass, String attributeName) { + if (rawValue == null) + { + return null; + } HashSet<T> set = new HashSet<T>(); if (rawValue instanceof Iterable) { diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java index 0d7dc1bb06..e0a736df89 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java @@ -27,12 +27,15 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import javax.net.ssl.KeyManagerFactory; + +import junit.framework.TestCase; + import org.apache.qpid.server.configuration.ConfigurationEntry; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.KeyStore; -import org.apache.qpid.server.model.TrustStore; - -import junit.framework.TestCase; +import org.apache.qpid.server.model.adapter.AbstractKeyStoreAdapter; +import org.apache.qpid.test.utils.TestSSLConstants; public class KeyStoreRecovererTest extends TestCase { @@ -40,6 +43,7 @@ public class KeyStoreRecovererTest extends TestCase public void testCreateWithAllAttributesProvided() { Map<String, Object> attributes = getKeyStoreAttributes(); + Map<String, Object> attributesCopy = new HashMap<String, Object>(attributes); UUID id = UUID.randomUUID(); Broker broker = mock(Broker.class); @@ -49,36 +53,27 @@ public class KeyStoreRecovererTest extends TestCase KeyStoreRecoverer recovever = new KeyStoreRecoverer(); - KeyStore KeyStore = recovever.create(null, entry, broker); - assertNotNull("Key store configured object is not created", KeyStore); - assertEquals(id, KeyStore.getId()); - assertEquals("my-secret-password", KeyStore.getPassword()); + KeyStore keyStore = recovever.create(null, entry, broker); + assertNotNull("Key store configured object is not created", keyStore); + assertEquals(id, keyStore.getId()); - assertNull("Password was unexpectedly returned from configured object", KeyStore.getAttribute(TrustStore.PASSWORD)); + //verify we can retrieve the actual password using the method + assertEquals(TestSSLConstants.BROKER_TRUSTSTORE_PASSWORD, keyStore.getPassword()); + assertNotNull(keyStore.getPassword()); - // password attribute should not be exposed by a key store configured object - // so, we should set password value to null in the map being used to create the key store configured object - attributes.put(KeyStore.PASSWORD, null); - for (Map.Entry<String, Object> attribute : attributes.entrySet()) + //verify that we havent configured the key store with the actual dummy password value + assertFalse(AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK.equals(keyStore.getPassword())); + + // Verify the remaining attributes, including that the password value returned + // via getAttribute is actually the dummy value and not the real password + attributesCopy.put(KeyStore.PASSWORD, AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK); + for (Map.Entry<String, Object> attribute : attributesCopy.entrySet()) { - Object attributeValue = KeyStore.getAttribute(attribute.getKey()); + Object attributeValue = keyStore.getAttribute(attribute.getKey()); assertEquals("Unexpected value of attribute '" + attribute.getKey() + "'", attribute.getValue(), attributeValue); } } - private Map<String, Object> getKeyStoreAttributes() - { - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(KeyStore.NAME, getName()); - attributes.put(KeyStore.PATH, "/path/to/KeyStore"); - attributes.put(KeyStore.PASSWORD, "my-secret-password"); - attributes.put(KeyStore.TYPE, "NON-JKS"); - attributes.put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, "NON-STANDARD"); - attributes.put(KeyStore.CERTIFICATE_ALIAS, "my-cert-alias"); - attributes.put(KeyStore.DESCRIPTION, "description"); - return attributes; - } - public void testCreateWithMissedRequiredAttributes() { Map<String, Object> attributes = getKeyStoreAttributes(); @@ -108,4 +103,16 @@ public class KeyStoreRecovererTest extends TestCase } } + private Map<String, Object> getKeyStoreAttributes() + { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(KeyStore.NAME, getName()); + attributes.put(KeyStore.PATH, TestSSLConstants.BROKER_KEYSTORE); + attributes.put(KeyStore.PASSWORD, TestSSLConstants.BROKER_KEYSTORE_PASSWORD); + attributes.put(KeyStore.TYPE, "jks"); + attributes.put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); + attributes.put(KeyStore.CERTIFICATE_ALIAS, "java-broker"); + return attributes; + } + } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java index 5e7784bc06..4d92f99306 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java @@ -27,16 +27,21 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import javax.net.ssl.TrustManagerFactory; + import org.apache.qpid.server.configuration.ConfigurationEntry; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.adapter.AbstractKeyStoreAdapter; import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestSSLConstants; public class TrustStoreRecovererTest extends QpidTestCase { public void testCreateWithAllAttributesProvided() { Map<String, Object> attributes = getTrustStoreAttributes(); + Map<String, Object> attributesCopy = new HashMap<String, Object>(attributes); UUID id = UUID.randomUUID(); Broker broker = mock(Broker.class); @@ -44,38 +49,29 @@ public class TrustStoreRecovererTest extends QpidTestCase when(entry.getAttributes()).thenReturn(attributes); when(entry.getId()).thenReturn(id); - TrustStoreRecoverer recovever = new TrustStoreRecoverer(); + TrustStoreRecoverer recoverer = new TrustStoreRecoverer(); - TrustStore trustStore = recovever.create(null, entry, broker); + TrustStore trustStore = recoverer.create(null, entry, broker); assertNotNull("Trust store configured object is not created", trustStore); assertEquals(id, trustStore.getId()); - assertEquals("my-secret-password", trustStore.getPassword()); - assertNull("Password was unexpectedly returned from configured object", trustStore.getAttribute(TrustStore.PASSWORD)); + //verify we can retrieve the actual password using the method + assertEquals(TestSSLConstants.BROKER_TRUSTSTORE_PASSWORD, trustStore.getPassword()); + assertNotNull(trustStore.getPassword()); + + //verify that we havent configured the trust store with the actual dummy password value + assertFalse(AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK.equals(trustStore.getPassword())); - // password attribute should not be exposed by a trust store configured object - // so, we should set password value to null in the map being used to create the trust store configured object - attributes.put(TrustStore.PASSWORD, null); - for (Map.Entry<String, Object> attribute : attributes.entrySet()) + // Verify the remaining attributes, including that the password value returned + // via getAttribute is actually the dummy value and not the real password + attributesCopy.put(TrustStore.PASSWORD, AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK); + for (Map.Entry<String, Object> attribute : attributesCopy.entrySet()) { Object attributeValue = trustStore.getAttribute(attribute.getKey()); assertEquals("Unexpected value of attribute '" + attribute.getKey() + "'", attribute.getValue(), attributeValue); } } - private Map<String, Object> getTrustStoreAttributes() - { - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(TrustStore.NAME, getName()); - attributes.put(TrustStore.PATH, "/path/to/truststore"); - attributes.put(TrustStore.PASSWORD, "my-secret-password"); - attributes.put(TrustStore.TYPE, "NON-JKS"); - attributes.put(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, "NON-STANDARD"); - attributes.put(TrustStore.PEERS_ONLY, Boolean.TRUE); - attributes.put(TrustStore.DESCRIPTION, "Description"); - return attributes; - } - public void testCreateWithMissedRequiredAttributes() { Map<String, Object> attributes = getTrustStoreAttributes(); @@ -106,4 +102,16 @@ public class TrustStoreRecovererTest extends QpidTestCase } } + private Map<String, Object> getTrustStoreAttributes() + { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(TrustStore.NAME, getName()); + attributes.put(TrustStore.PATH, TestSSLConstants.BROKER_TRUSTSTORE); + attributes.put(TrustStore.PASSWORD, TestSSLConstants.BROKER_TRUSTSTORE_PASSWORD); + attributes.put(TrustStore.TYPE, "jks"); + attributes.put(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM, TrustManagerFactory.getDefaultAlgorithm()); + attributes.put(TrustStore.PEERS_ONLY, Boolean.TRUE); + return attributes; + } + } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java index adb4472694..7d253d56f0 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java @@ -284,7 +284,7 @@ public abstract class ConfigurationEntryStoreTestCase extends QpidTestCase attributes.put(TrustStore.PATH, "/path/to/truststore"); attributes.put(TrustStore.PASSWORD, "my-secret-password"); attributes.put(TrustStore.TYPE, "NON-JKS"); - attributes.put(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, "NON-STANDARD"); + attributes.put(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM, "NON-STANDARD"); attributes.put(TrustStore.DESCRIPTION, "Description"); ConfigurationEntry trustStoreEntry = new ConfigurationEntry(trustStoreId, TrustStore.class.getSimpleName(), attributes, diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java index a9303c264e..53fb1a0620 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java @@ -22,8 +22,8 @@ package org.apache.qpid.server.model.adapter; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.any; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -47,8 +47,6 @@ import org.apache.qpid.test.utils.QpidTestCase; public class PortFactoryTest extends QpidTestCase { private UUID _portId = UUID.randomUUID(); - private UUID _keyStoreId = UUID.randomUUID(); - private UUID _trustStoreId = UUID.randomUUID(); private int _portNumber = 123; private Set<String> _tcpStringSet = Collections.singleton(Transport.TCP.name()); private Set<Transport> _tcpTransportSet = Collections.singleton(Transport.TCP); @@ -134,15 +132,14 @@ public class PortFactoryTest extends QpidTestCase public void testCreateAmqpPort() { - createAmqpPortTestImpl(false,false,false); + createAmqpPortTestImpl(false, false, false, null, null); } public void testCreateAmqpPortUsingSslFailsWithoutKeyStore() { - when(_broker.getKeyStores()).thenReturn(new ArrayList<KeyStore>()); try { - createAmqpPortTestImpl(true,false,false); + createAmqpPortTestImpl(true, false, false, null, null); fail("expected exception due to lack of SSL keystore"); } catch(IllegalConfigurationException e) @@ -153,18 +150,22 @@ public class PortFactoryTest extends QpidTestCase public void testCreateAmqpPortUsingSslSucceedsWithKeyStore() { - when(_broker.getKeyStores()).thenReturn(Collections.singleton(_keyStore)); + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); - createAmqpPortTestImpl(true,false,false); + createAmqpPortTestImpl(true, false, false, keyStoreName, null); } public void testCreateAmqpPortNeedingClientAuthFailsWithoutTrustStore() { - when(_broker.getKeyStores()).thenReturn(Collections.singleton(_keyStore)); - when(_broker.getTrustStores()).thenReturn(new ArrayList<TrustStore>()); + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + when(_broker.findTrustStoreByName(any(String.class))).thenReturn(null); + try { - createAmqpPortTestImpl(true,true,false); + createAmqpPortTestImpl(true, true, false, keyStoreName, null); fail("expected exception due to lack of SSL truststore"); } catch(IllegalConfigurationException e) @@ -175,19 +176,23 @@ public class PortFactoryTest extends QpidTestCase public void testCreateAmqpPortNeedingClientAuthSucceedsWithTrustStore() { - when(_broker.getKeyStores()).thenReturn(Collections.singleton(_keyStore)); - when(_broker.getTrustStores()).thenReturn(Collections.singleton(_trustStore)); + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); - createAmqpPortTestImpl(true,true,false); + String trustStoreName = "myTrustStore"; + when(_broker.findTrustStoreByName(trustStoreName)).thenReturn(_trustStore); + + createAmqpPortTestImpl(true, true, false, keyStoreName, new String[]{trustStoreName}); } public void testCreateAmqpPortWantingClientAuthFailsWithoutTrustStore() { - when(_broker.getKeyStores()).thenReturn(Collections.singleton(_keyStore)); - when(_broker.getTrustStores()).thenReturn(new ArrayList<TrustStore>()); + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + try { - createAmqpPortTestImpl(true,false,true); + createAmqpPortTestImpl(true, false, true, keyStoreName, null); fail("expected exception due to lack of SSL truststore"); } catch(IllegalConfigurationException e) @@ -198,13 +203,17 @@ public class PortFactoryTest extends QpidTestCase public void testCreateAmqpPortWantingClientAuthSucceedsWithTrustStore() { - when(_broker.getKeyStores()).thenReturn(Collections.singleton(_keyStore)); - when(_broker.getTrustStores()).thenReturn(Collections.singleton(_trustStore)); + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); - createAmqpPortTestImpl(true,false,true); + String trustStoreName = "myTrustStore"; + when(_broker.findTrustStoreByName(trustStoreName)).thenReturn(_trustStore); + + createAmqpPortTestImpl(true, false, true, keyStoreName, new String[]{trustStoreName}); } - public void createAmqpPortTestImpl(boolean useSslTransport, boolean needClientAuth, boolean wantClientAuth) + public void createAmqpPortTestImpl(boolean useSslTransport, boolean needClientAuth, boolean wantClientAuth, + String keystoreName, String[] trustStoreNames) { Set<Protocol> amqp010ProtocolSet = Collections.singleton(Protocol.AMQP_0_10); Set<String> amqp010StringSet = Collections.singleton(Protocol.AMQP_0_10.name()); @@ -225,6 +234,16 @@ public class PortFactoryTest extends QpidTestCase _attributes.put(Port.WANT_CLIENT_AUTH, "true"); } + if(keystoreName != null) + { + _attributes.put(Port.KEY_STORE, keystoreName); + } + + if(trustStoreNames != null) + { + _attributes.put(Port.TRUST_STORES, Arrays.asList(trustStoreNames)); + } + Port port = _portFactory.createPort(_portId, _broker, _attributes); assertNotNull(port); @@ -335,4 +354,29 @@ public class PortFactoryTest extends QpidTestCase // pass } } + + public void testCreateRMIPortRequestingSslFails() + { + String keyStoreName = "myKeyStore"; + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(Port.PORT, 1); + attributes.put(Port.NAME, getTestName()); + attributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); + attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.RMI)); + _attributes.put(Port.KEY_STORE, keyStoreName); + + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + try + { + _portFactory.createPort(_portId, _broker, attributes); + fail("RMI port creation should fail due to requesting SSL"); + } + catch(IllegalConfigurationException e) + { + e.printStackTrace(); + // pass + } + } } diff --git a/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java index 01381ad23f..158006f072 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java @@ -31,7 +31,6 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import java.security.cert.X509Certificate; import java.io.IOException; import java.security.GeneralSecurityException; @@ -39,7 +38,6 @@ import java.security.KeyStore; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; /** * Factory used to create SSLContexts. SSL needs to be configured diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestSSLConstants.java b/qpid/java/common/src/test/java/org/apache/qpid/test/utils/TestSSLConstants.java index 5664e94bd9..c48f164d98 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestSSLConstants.java +++ b/qpid/java/common/src/test/java/org/apache/qpid/test/utils/TestSSLConstants.java @@ -28,4 +28,10 @@ public interface TestSSLConstants String BROKER_KEYSTORE = "test-profiles/test_resources/ssl/java_broker_keystore.jks"; String BROKER_KEYSTORE_PASSWORD = "password"; + + String BROKER_PEERSTORE = "test-profiles/test_resources/ssl/java_broker_peerstore.jks"; + String BROKER_PEERSTORE_PASSWORD = "password"; + + String BROKER_TRUSTSTORE = "test-profiles/test_resources/ssl/java_broker_truststore.jks"; + String BROKER_TRUSTSTORE_PASSWORD = "password"; } diff --git a/qpid/java/systests/etc/config-systests.json b/qpid/java/systests/etc/config-systests.json index b06b469891..ec3d17dbec 100644 --- a/qpid/java/systests/etc/config-systests.json +++ b/qpid/java/systests/etc/config-systests.json @@ -22,15 +22,21 @@ "name": "QpidBroker", "defaultAuthenticationProvider" : "plain", "defaultVirtualHost" : "test", - "keyStorePath": "${QPID_HOME}/../test-profiles/test_resources/ssl/java_broker_keystore.jks", - "keyStorePassword": "password", - "trustStorePath": "${QPID_HOME}/../test-profiles/test_resources/ssl/java_broker_truststore.jks", - "trustStorePassword": "password", "authenticationproviders" : [ { "name" : "plain", "type" : "PlainPasswordFile", "path" : "${QPID_HOME}/etc/passwd" } ], + "keystores" : [ { + "name" : "systestsKeyStore", + "path" : "${QPID_HOME}/../test-profiles/test_resources/ssl/java_broker_keystore.jks", + "password" : "password" + } ], + "truststores" : [ { + "name" : "systestsTrustStore", + "path" : "${QPID_HOME}/../test-profiles/test_resources/ssl/java_broker_truststore.jks", + "password" : "password" + } ], "ports" : [ { "name" : "amqp", "port" : "${test.port}" diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java index 884e89fb65..e2cd3e254e 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java @@ -365,6 +365,8 @@ public class SSLTest extends QpidBrokerTestCase sslPortAttributes.put(Port.NEED_CLIENT_AUTH, needClientAuth); sslPortAttributes.put(Port.WANT_CLIENT_AUTH, wantClientAuth); sslPortAttributes.put(Port.NAME, TestBrokerConfiguration.ENTRY_NAME_SSL_PORT); + sslPortAttributes.put(Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); + sslPortAttributes.put(Port.TRUST_STORES, Collections.singleton(TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE)); getBrokerConfiguration().addPortConfiguration(sslPortAttributes); } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationTest.java index 2e051d93dd..90c6b12779 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationTest.java @@ -20,12 +20,16 @@ */ package org.apache.qpid.server.security.auth.manager; +import static org.apache.qpid.test.utils.TestSSLConstants.BROKER_PEERSTORE; +import static org.apache.qpid.test.utils.TestSSLConstants.BROKER_PEERSTORE_PASSWORD; import static org.apache.qpid.test.utils.TestSSLConstants.KEYSTORE; import static org.apache.qpid.test.utils.TestSSLConstants.KEYSTORE_PASSWORD; import static org.apache.qpid.test.utils.TestSSLConstants.TRUSTSTORE; import static org.apache.qpid.test.utils.TestSSLConstants.TRUSTSTORE_PASSWORD; import static org.apache.qpid.test.utils.TestSSLConstants.UNTRUSTED_KEYSTORE; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -41,6 +45,7 @@ import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.plugin.AuthenticationManagerFactory; import org.apache.qpid.test.utils.JMXTestUtils; import org.apache.qpid.test.utils.QpidBrokerTestCase; @@ -52,6 +57,7 @@ public class ExternalAuthenticationTest extends QpidBrokerTestCase protected void setUp() throws Exception { // not calling super.setUp() to avoid broker start-up + setSystemProperty("javax.net.debug", "ssl"); } /** @@ -168,31 +174,87 @@ public class ExternalAuthenticationTest extends QpidBrokerTestCase } /** - * Tests that when using the EXTERNAL auth provide and the broker 'peerstore' is configured to contain a certificate that is - * otherwise untrusted by the broker [truststore], clients using that certificate will then be able to connect. + * Tests that when using the EXTERNAL auth provider and a 'peersOnly' truststore, clients using certs directly in + * in the store will be able to connect and clients using certs signed by the same CA but not in the store will not. */ - public void testExternalAuthenticationWithPeerStoreAllowsOtherwiseUntrustedClientCert() throws Exception + public void testExternalAuthenticationWithPeersOnlyTrustStore() throws Exception { - setCommonBrokerSSLProperties(true); - getBrokerConfiguration().setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_SSL_PORT, Port.AUTHENTICATION_PROVIDER, TestBrokerConfiguration.ENTRY_NAME_EXTERNAL_PROVIDER); + externalAuthenticationWithPeersOnlyTrustStoreTestImpl(false); + } + + /** + * Tests that when using the EXTERNAL auth provider, with both the regular trust store and a 'peersOnly' truststore, clients + * using certs signed by the CA in the trust store are allowed even if they are not present in the 'peersOnly' store. + */ + public void testExternalAuthenticationWithRegularAndPeersOnlyTrustStores() throws Exception + { + externalAuthenticationWithPeersOnlyTrustStoreTestImpl(true); + } + + private void externalAuthenticationWithPeersOnlyTrustStoreTestImpl(boolean useTrustAndPeerStore) throws Exception + { + String peerStoreName = "myPeerStore"; + + List<String> storeNames = null; + if(useTrustAndPeerStore) + { + //Use the regular trust store AND the 'peersOnly' store. The regular trust store trusts the CA that + //signed both the app1 and app2 certs. The peersOnly store contains only app1 and so does not trust app2 + storeNames = Arrays.asList(TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE, peerStoreName); + } + else + { + //use only the 'peersOnly' store, which contains only app1 and so does not trust app2 + storeNames = Arrays.asList(peerStoreName); + } + + //set the brokers SSL config, inc which SSL stores to use + setCommonBrokerSSLProperties(true, storeNames); - //Use the untrusted client keystore as the brokers peerstore to make the broker trust the cert. - getBrokerConfiguration().setBrokerAttribute(Broker.PEER_STORE_PATH, UNTRUSTED_KEYSTORE); - getBrokerConfiguration().setBrokerAttribute(Broker.PEER_STORE_PASSWORD, KEYSTORE_PASSWORD); + //add the peersOnly store to the config + Map<String, Object> sslTrustStoreAttributes = new HashMap<String, Object>(); + sslTrustStoreAttributes.put(TrustStore.NAME, peerStoreName); + sslTrustStoreAttributes.put(TrustStore.PATH, BROKER_PEERSTORE); + sslTrustStoreAttributes.put(TrustStore.PASSWORD, BROKER_PEERSTORE_PASSWORD); + sslTrustStoreAttributes.put(TrustStore.PEERS_ONLY, true); + getBrokerConfiguration().addTrustStoreConfiguration(sslTrustStoreAttributes); + + getBrokerConfiguration().setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_SSL_PORT, Port.AUTHENTICATION_PROVIDER, TestBrokerConfiguration.ENTRY_NAME_EXTERNAL_PROVIDER); super.setUp(); - setUntrustedClientKeystoreProperties(); + setClientKeystoreProperties(); setClientTrustoreProperties(); try { - getExternalSSLConnection(false); - fail("Untrusted client's validation against the broker's multi store manager unexpectedly passed."); + //use the app1 cert, which IS in the peerstore (and has CA in the trustStore) + getExternalSSLConnection(false, "&ssl_cert_alias='app1'"); + } + catch (JMSException e) + { + fail("Client's validation against the broker's multi store manager unexpectedly failed, when configured store was expected to allow."); + } + + try + { + //use the app2 cert, which is NOT in the peerstore (but is signed by the same CA as app1) + getExternalSSLConnection(false, "&ssl_cert_alias='app2'"); + if(!useTrustAndPeerStore) + { + fail("Client's validation against the broker's multi store manager unexpectedly passed, when configured store was expected to deny."); + } } catch (JMSException e) { - // expected + if(useTrustAndPeerStore) + { + fail("Client's validation against the broker's multi store manager unexpectedly failed, when configured store was expected to allow."); + } + else + { + //expected, the CA in trust store should allow both app1 and app2 + } } } @@ -215,7 +277,7 @@ public class ExternalAuthenticationTest extends QpidBrokerTestCase try { - getExternalSSLConnection(false); + getExternalSSLConnection(false, "&ssl_cert_alias='app2'"); } catch (JMSException e) { @@ -250,7 +312,7 @@ public class ExternalAuthenticationTest extends QpidBrokerTestCase try { - getExternalSSLConnection(false); + getExternalSSLConnection(false, "&ssl_cert_alias='app2'"); } catch (JMSException e) { @@ -267,26 +329,38 @@ public class ExternalAuthenticationTest extends QpidBrokerTestCase private Connection getExternalSSLConnection(boolean includeUserNameAndPassword) throws Exception { - String url = "amqp://%s@test/?brokerlist='tcp://localhost:%s?ssl='true'&sasl_mechs='EXTERNAL'&ssl_cert_alias='app2''"; + return getExternalSSLConnection(includeUserNameAndPassword, ""); + } + + private Connection getExternalSSLConnection(boolean includeUserNameAndPassword, String optionString) throws Exception + { + String url = "amqp://%s@test/?brokerlist='tcp://localhost:%s?ssl='true'&sasl_mechs='EXTERNAL'%s'"; if (includeUserNameAndPassword) { - url = String.format(url, "guest:guest", String.valueOf(QpidBrokerTestCase.DEFAULT_SSL_PORT)); + url = String.format(url, "guest:guest", String.valueOf(QpidBrokerTestCase.DEFAULT_SSL_PORT), optionString); } else { - url = String.format(url, ":", String.valueOf(QpidBrokerTestCase.DEFAULT_SSL_PORT)); + url = String.format(url, ":", String.valueOf(QpidBrokerTestCase.DEFAULT_SSL_PORT), optionString); } return getConnection(new AMQConnectionURL(url)); } private void setCommonBrokerSSLProperties(boolean needClientAuth) throws ConfigurationException { + setCommonBrokerSSLProperties(needClientAuth, Collections.singleton(TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE)); + } + + private void setCommonBrokerSSLProperties(boolean needClientAuth, Collection<String> trustStoreNames) throws ConfigurationException + { TestBrokerConfiguration config = getBrokerConfiguration(); Map<String, Object> sslPortAttributes = new HashMap<String, Object>(); sslPortAttributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); sslPortAttributes.put(Port.PORT, DEFAULT_SSL_PORT); sslPortAttributes.put(Port.NEED_CLIENT_AUTH, String.valueOf(needClientAuth)); sslPortAttributes.put(Port.NAME, TestBrokerConfiguration.ENTRY_NAME_SSL_PORT); + sslPortAttributes.put(Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); + sslPortAttributes.put(Port.TRUST_STORES, trustStoreNames); config.addPortConfiguration(sslPortAttributes); Map<String, Object> externalAuthProviderAttributes = new HashMap<String, Object>(); @@ -311,6 +385,5 @@ public class ExternalAuthenticationTest extends QpidBrokerTestCase { setSystemProperty("javax.net.ssl.trustStore", TRUSTSTORE); setSystemProperty("javax.net.ssl.trustStorePassword", TRUSTSTORE_PASSWORD); - setSystemProperty("javax.net.debug", "ssl"); } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/MultipleAuthenticationManagersTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/MultipleAuthenticationManagersTest.java index 40346d7424..44057025ba 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/MultipleAuthenticationManagersTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/auth/manager/MultipleAuthenticationManagersTest.java @@ -56,6 +56,8 @@ public class MultipleAuthenticationManagersTest extends QpidBrokerTestCase sslPortAttributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); sslPortAttributes.put(Port.PORT, DEFAULT_SSL_PORT); sslPortAttributes.put(Port.NAME, TestBrokerConfiguration.ENTRY_NAME_SSL_PORT); + sslPortAttributes.put(Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); + sslPortAttributes.put(Port.TRUST_STORES, Collections.singleton(TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE)); sslPortAttributes.put(Port.AUTHENTICATION_PROVIDER, TestBrokerConfiguration.ENTRY_NAME_ANONYMOUS_PROVIDER); config.addPortConfiguration(sslPortAttributes); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java index 9279287117..4ec38fbe23 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java @@ -314,11 +314,9 @@ public class ManagementLoggingTest extends AbstractTestLogging if(useManagementSSL) { - // This test requires we have an ssl connection + // This test requires we have ssl, change the transport and add they keystore to the port config config.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_JMX_PORT, Port.TRANSPORTS, Collections.singleton(Transport.SSL)); - - setSystemProperty("javax.net.ssl.keyStore", "test-profiles/test_resources/ssl/java_broker_keystore.jks"); - setSystemProperty("javax.net.ssl.keyStorePassword", "password"); + config.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_JMX_PORT, Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); } startBroker(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java index e20db6a6ac..6f795cc61d 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java @@ -207,14 +207,14 @@ public class Asserts } if (isAMQPPort) { - assertAttributesPresent(port, Port.AVAILABLE_ATTRIBUTES, Port.CREATED, Port.UPDATED, Port.AUTHENTICATION_PROVIDER); + assertAttributesPresent(port, Port.AVAILABLE_ATTRIBUTES, Port.CREATED, Port.UPDATED, Port.AUTHENTICATION_PROVIDER, Port.KEY_STORE, Port.TRUST_STORES); assertNotNull("Unexpected value of attribute " + Port.BINDING_ADDRESS, port.get(Port.BINDING_ADDRESS)); } else { assertAttributesPresent(port, Port.AVAILABLE_ATTRIBUTES, Port.CREATED, Port.UPDATED, Port.AUTHENTICATION_PROVIDER, Port.BINDING_ADDRESS, Port.TCP_NO_DELAY, Port.SEND_BUFFER_SIZE, Port.RECEIVE_BUFFER_SIZE, - Port.NEED_CLIENT_AUTH, Port.WANT_CLIENT_AUTH); + Port.NEED_CLIENT_AUTH, Port.WANT_CLIENT_AUTH, Port.KEY_STORE, Port.TRUST_STORES); } @SuppressWarnings("unchecked") diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BasicAuthRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BasicAuthRestTest.java index 22fb70fa68..ea63cc7f4e 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BasicAuthRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BasicAuthRestTest.java @@ -58,6 +58,8 @@ public class BasicAuthRestTest extends QpidRestTestCase if (useSsl) { getBrokerConfiguration().setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_PORT, Port.PROTOCOLS, Collections.singleton(Protocol.HTTPS)); + getBrokerConfiguration().setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_PORT, Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); + } super.customizeConfiguration(); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestHttpsTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestHttpsTest.java index 06927946ba..7fd13ed8aa 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestHttpsTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestHttpsTest.java @@ -54,6 +54,7 @@ public class BrokerRestHttpsTest extends QpidRestTestCase Map<String, Object> newAttributes = new HashMap<String, Object>(); newAttributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.HTTPS)); newAttributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); + newAttributes.put(Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); getBrokerConfiguration().setObjectAttributes(TestBrokerConfiguration.ENTRY_NAME_HTTP_PORT,newAttributes); } @@ -63,7 +64,6 @@ public class BrokerRestHttpsTest extends QpidRestTestCase Asserts.assertAttributesPresent(brokerDetails, Broker.AVAILABLE_ATTRIBUTES, Broker.BYTES_RETAINED, Broker.PROCESS_PID, Broker.SUPPORTED_STORE_TYPES, Broker.CREATED, Broker.TIME_TO_LIVE, Broker.UPDATED, - Broker.ACL_FILE, Broker.KEY_STORE_CERT_ALIAS, Broker.TRUST_STORE_PATH, Broker.TRUST_STORE_PASSWORD, - Broker.GROUP_FILE, Broker.PEER_STORE_PATH, Broker.PEER_STORE_PASSWORD); + Broker.ACL_FILE, Broker.GROUP_FILE); } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java index a795063750..f0e4c1d02a 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java @@ -142,13 +142,6 @@ public class BrokerRestTest extends QpidRestTestCase invalidAttributes.put(Broker.CONNECTION_HEART_BEAT_DELAY, -11000); invalidAttributes.put(Broker.STATISTICS_REPORTING_PERIOD, -12000); invalidAttributes.put(Broker.ACL_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-acl.acl"); - invalidAttributes.put(Broker.KEY_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-keystore.jks"); - invalidAttributes.put(Broker.KEY_STORE_PASSWORD, "password1"); - invalidAttributes.put(Broker.KEY_STORE_CERT_ALIAS, "java-broker1"); - invalidAttributes.put(Broker.TRUST_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-truststore.jks"); - invalidAttributes.put(Broker.TRUST_STORE_PASSWORD, "password2"); - invalidAttributes.put(Broker.PEER_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-peerstore.jks"); - invalidAttributes.put(Broker.PEER_STORE_PASSWORD, "password3"); invalidAttributes.put(Broker.GROUP_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "groups-non-existing"); invalidAttributes.put(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, -13000); invalidAttributes.put(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, -14000); @@ -191,13 +184,6 @@ public class BrokerRestTest extends QpidRestTestCase brokerAttributes.put(Broker.STATISTICS_REPORTING_PERIOD, 12000); brokerAttributes.put(Broker.STATISTICS_REPORTING_RESET_ENABLED, true); brokerAttributes.put(Broker.ACL_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "broker_example.acl"); - brokerAttributes.put(Broker.KEY_STORE_PATH, TestSSLConstants.BROKER_KEYSTORE); - brokerAttributes.put(Broker.KEY_STORE_PASSWORD, TestSSLConstants.BROKER_KEYSTORE_PASSWORD); - brokerAttributes.put(Broker.KEY_STORE_CERT_ALIAS, "java-broker"); - brokerAttributes.put(Broker.TRUST_STORE_PATH, TestSSLConstants.TRUSTSTORE); - brokerAttributes.put(Broker.TRUST_STORE_PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); - brokerAttributes.put(Broker.PEER_STORE_PATH, TestSSLConstants.TRUSTSTORE); - brokerAttributes.put(Broker.PEER_STORE_PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); brokerAttributes.put(Broker.GROUP_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "groups"); brokerAttributes.put(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, 13000); brokerAttributes.put(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, 14000); @@ -212,10 +198,7 @@ public class BrokerRestTest extends QpidRestTestCase { String attributeName = entry.getKey(); Object attributeValue = entry.getValue(); - if (attributeName.equals(Broker.KEY_STORE_PASSWORD) || attributeName.equals(Broker.TRUST_STORE_PASSWORD) || attributeName.equals(Broker.PEER_STORE_PASSWORD)) - { - attributeValue = "********"; - } + Object currentValue = actualAttributes.get(attributeName); assertEquals("Unexpected attribute " + attributeName + " value:", attributeValue, currentValue); } @@ -225,10 +208,7 @@ public class BrokerRestTest extends QpidRestTestCase { Asserts.assertAttributesPresent(brokerDetails, Broker.AVAILABLE_ATTRIBUTES, Broker.BYTES_RETAINED, Broker.PROCESS_PID, Broker.SUPPORTED_STORE_TYPES, - Broker.CREATED, Broker.TIME_TO_LIVE, Broker.UPDATED, Broker.ACL_FILE, - Broker.KEY_STORE_PATH, Broker.KEY_STORE_PASSWORD, Broker.KEY_STORE_CERT_ALIAS, - Broker.TRUST_STORE_PATH, Broker.TRUST_STORE_PASSWORD, Broker.GROUP_FILE, - Broker.PEER_STORE_PATH, Broker.PEER_STORE_PASSWORD); + Broker.CREATED, Broker.TIME_TO_LIVE, Broker.UPDATED, Broker.ACL_FILE, Broker.GROUP_FILE); assertEquals("Unexpected value of attribute " + Broker.BUILD_VERSION, QpidProperties.getBuildVersion(), brokerDetails.get(Broker.BUILD_VERSION)); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/KeyStoreRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/KeyStoreRestTest.java new file mode 100644 index 0000000000..149ddcfcbb --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/KeyStoreRestTest.java @@ -0,0 +1,269 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +package org.apache.qpid.systest.rest; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.adapter.AbstractKeyStoreAdapter; +import org.apache.qpid.test.utils.TestBrokerConfiguration; +import org.apache.qpid.test.utils.TestSSLConstants; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; + +public class KeyStoreRestTest extends QpidRestTestCase +{ + @Override + public void setUp() throws Exception + { + // not calling super.setUp() to avoid broker start-up until + // after any necessary configuration + } + + public void testGet() throws Exception + { + super.setUp(); + + //verify existence of the default keystore used by the systests + List<Map<String, Object>> keyStores = assertNumberOfKeyStores(1); + + Map<String, Object> keystore = keyStores.get(0); + assertKeyStoreAttributes(keystore, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE, + System.getProperty(QPID_HOME) + "/../" + TestSSLConstants.BROKER_KEYSTORE, null); + } + + public void testCreate() throws Exception + { + super.setUp(); + + String name = getTestName(); + String certAlias = "app2"; + + assertNumberOfKeyStores(1); + createKeyStore(name, certAlias); + assertNumberOfKeyStores(2); + + List<Map<String, Object>> keyStores = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details cannot be null", keyStores); + + assertKeyStoreAttributes(keyStores.get(0), name, TestSSLConstants.KEYSTORE, certAlias); + } + + public void testDelete() throws Exception + { + super.setUp(); + + String name = getTestName(); + String certAlias = "app2"; + + assertNumberOfKeyStores(1); + createKeyStore(name, certAlias); + assertNumberOfKeyStores(2); + + int responseCode = getRestTestHelper().submitRequest("/rest/keystore/" + name , "DELETE", null); + assertEquals("Unexpected response code for provider deletion", 200, responseCode); + + List<Map<String, Object>> keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + assertTrue("details should be empty as the keystore no longer exists", keyStore.isEmpty()); + + //check only the default systests key store remains + List<Map<String, Object>> keyStores = assertNumberOfKeyStores(1); + Map<String, Object> keystore = keyStores.get(0); + assertKeyStoreAttributes(keystore, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE, + System.getProperty(QPID_HOME) + "/../" + TestSSLConstants.BROKER_KEYSTORE, null); + } + + public void testDeleteFailsWhenKeyStoreInUse() throws Exception + { + String name = "testDeleteFailsWhenKeyStoreInUse"; + + //add a new key store config to use + Map<String, Object> sslKeyStoreAttributes = new HashMap<String, Object>(); + sslKeyStoreAttributes.put(KeyStore.NAME, name); + sslKeyStoreAttributes.put(KeyStore.PATH, TestSSLConstants.BROKER_KEYSTORE); + sslKeyStoreAttributes.put(KeyStore.PASSWORD, TestSSLConstants.BROKER_KEYSTORE_PASSWORD); + getBrokerConfiguration().addKeyStoreConfiguration(sslKeyStoreAttributes); + + //add the SSL port using it + Map<String, Object> sslPortAttributes = new HashMap<String, Object>(); + sslPortAttributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); + sslPortAttributes.put(Port.PORT, DEFAULT_SSL_PORT); + sslPortAttributes.put(Port.NAME, TestBrokerConfiguration.ENTRY_NAME_SSL_PORT); + sslPortAttributes.put(Port.KEY_STORE, name); + getBrokerConfiguration().addPortConfiguration(sslPortAttributes); + + super.setUp(); + + //verify the keystore is there + assertNumberOfKeyStores(2); + + List<Map<String, Object>> keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + assertKeyStoreAttributes(keyStore.get(0), name, TestSSLConstants.BROKER_KEYSTORE, null); + + //try to delete it, which should fail as it is in use + int responseCode = getRestTestHelper().submitRequest("/rest/keystore/" + name , "DELETE", null); + assertEquals("Unexpected response code for provider deletion", 409, responseCode); + + //check its still there + assertNumberOfKeyStores(2); + keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + assertKeyStoreAttributes(keyStore.get(0), name, TestSSLConstants.BROKER_KEYSTORE, null); + } + + public void testUpdateWithGoodPathSucceeds() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfKeyStores(1); + createKeyStore(name, null); + assertNumberOfKeyStores(2); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(KeyStore.NAME, name); + attributes.put(KeyStore.PATH, TestSSLConstants.UNTRUSTED_KEYSTORE); + + int responseCode = getRestTestHelper().submitRequest("/rest/keystore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for keystore update", 200, responseCode); + + List<Map<String, Object>> keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + + assertKeyStoreAttributes(keyStore.get(0), name, TestSSLConstants.UNTRUSTED_KEYSTORE, null); + } + + public void testUpdateWithNonExistentPathFails() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfKeyStores(1); + createKeyStore(name, null); + assertNumberOfKeyStores(2); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(KeyStore.NAME, name); + attributes.put(KeyStore.PATH, "does.not.exist"); + + int responseCode = getRestTestHelper().submitRequest("/rest/keystore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for keystore update", 409, responseCode); + + List<Map<String, Object>> keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + + //verify the details remain unchanged + assertKeyStoreAttributes(keyStore.get(0), name, TestSSLConstants.KEYSTORE, null); + } + + public void testUpdateCertificateAlias() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfKeyStores(1); + createKeyStore(name, "app1"); + assertNumberOfKeyStores(2); + + List<Map<String, Object>> keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + assertKeyStoreAttributes(keyStore.get(0), name, TestSSLConstants.KEYSTORE, "app1"); + + //Update the certAlias from app1 to app2 + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(KeyStore.NAME, name); + attributes.put(KeyStore.CERTIFICATE_ALIAS, "app2"); + + int responseCode = getRestTestHelper().submitRequest("/rest/keystore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for keystore update", 200, responseCode); + + keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + + assertKeyStoreAttributes(keyStore.get(0), name, TestSSLConstants.KEYSTORE, "app2"); + + //Update the certAlias to clear it (i.e go from from app1 to null) + attributes = new HashMap<String, Object>(); + attributes.put(KeyStore.NAME, name); + attributes.put(KeyStore.CERTIFICATE_ALIAS, null); + + responseCode = getRestTestHelper().submitRequest("/rest/keystore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for keystore update", 200, responseCode); + + keyStore = getRestTestHelper().getJsonAsList("/rest/keystore/" + name); + assertNotNull("details should not be null", keyStore); + + assertKeyStoreAttributes(keyStore.get(0), name, TestSSLConstants.KEYSTORE, null); + } + + private List<Map<String, Object>> assertNumberOfKeyStores(int numberOfKeystores) throws IOException, + JsonParseException, JsonMappingException + { + List<Map<String, Object>> keyStores = getRestTestHelper().getJsonAsList("/rest/keystore"); + assertNotNull("keystores should not be null", keyStores); + assertEquals("Unexpected number of keystores", numberOfKeystores, keyStores.size()); + + return keyStores; + } + + private void createKeyStore(String name, String certAlias) throws IOException, JsonGenerationException, JsonMappingException + { + Map<String, Object> keyStoreAttributes = new HashMap<String, Object>(); + keyStoreAttributes.put(KeyStore.NAME, name); + keyStoreAttributes.put(KeyStore.PATH, TestSSLConstants.KEYSTORE); + keyStoreAttributes.put(KeyStore.PASSWORD, TestSSLConstants.KEYSTORE_PASSWORD); + keyStoreAttributes.put(KeyStore.CERTIFICATE_ALIAS, certAlias); + + int responseCode = getRestTestHelper().submitRequest("/rest/keystore/" + name, "PUT", keyStoreAttributes); + assertEquals("Unexpected response code", 201, responseCode); + } + + private void assertKeyStoreAttributes(Map<String, Object> keystore, String name, String path, String certAlias) + { + assertEquals("default systests key store is missing", + name, keystore.get(KeyStore.NAME)); + assertEquals("unexpected path to key store", + path, keystore.get(KeyStore.PATH)); + assertEquals("unexpected (dummy) password of default systests key store", + AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK, keystore.get(KeyStore.PASSWORD)); + assertEquals("unexpected type of default systests key store", + java.security.KeyStore.getDefaultType(), keystore.get(KeyStore.TYPE)); + assertEquals("unexpected certificateAlias value", + certAlias, keystore.get(KeyStore.CERTIFICATE_ALIAS)); + if(certAlias == null) + { + assertFalse("should not be a certificateAlias attribute", + keystore.containsKey(KeyStore.CERTIFICATE_ALIAS)); + } + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/PortRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/PortRestTest.java index 1497d740dc..8ec9e50fa9 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/PortRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/PortRestTest.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.Map; import org.apache.qpid.server.model.AuthenticationProvider; -import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.State; @@ -205,6 +204,7 @@ public class PortRestTest extends QpidRestTestCase Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put(Port.NAME, portName); attributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); + attributes.put(Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); int responseCode = getRestTestHelper().submitRequest("/rest/port/" + portName, "PUT", attributes); assertEquals("Transport has not been changed to SSL " , 200, responseCode); @@ -217,12 +217,13 @@ public class PortRestTest extends QpidRestTestCase Collection<String> transports = (Collection<String>) port.get(Port.TRANSPORTS); assertEquals("Unexpected auth provider", new HashSet<String>(Arrays.asList(Transport.SSL.name())), new HashSet<String>(transports)); + + String keyStore = (String) port.get(Port.KEY_STORE); + assertEquals("Unexpected auth provider", TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE, keyStore); } public void testUpdateTransportFromTCPToSSLWithoutKeystoreConfiguredFails() throws Exception { - getBrokerConfiguration().setBrokerAttribute(Broker.KEY_STORE_PATH, null); - getBrokerConfiguration().setSaved(false); restartBrokerInManagementMode(); String portName = TestBrokerConfiguration.ENTRY_NAME_AMQP_PORT; @@ -241,6 +242,8 @@ public class PortRestTest extends QpidRestTestCase attributes.put(Port.NAME, portName); attributes.put(Port.PORT, DEFAULT_SSL_PORT); attributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); + attributes.put(Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); + attributes.put(Port.TRUST_STORES, Collections.singleton(TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE)); int responseCode = getRestTestHelper().submitRequest("/rest/port/" + portName, "PUT", attributes); assertEquals("SSL port was not added", 201, responseCode); @@ -257,6 +260,11 @@ public class PortRestTest extends QpidRestTestCase Map<String, Object> port = getRestTestHelper().getJsonAsSingletonList("/rest/port/" + portName); assertEquals("Unexpected " + Port.NEED_CLIENT_AUTH, true, port.get(Port.NEED_CLIENT_AUTH)); assertEquals("Unexpected " + Port.WANT_CLIENT_AUTH, true, port.get(Port.WANT_CLIENT_AUTH)); + assertEquals("Unexpected " + Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE, port.get(Port.KEY_STORE)); + @SuppressWarnings("unchecked") + Collection<String> trustStores = (Collection<String>) port.get(Port.TRUST_STORES); + assertEquals("Unexpected auth provider", new HashSet<String>(Arrays.asList(TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE)), + new HashSet<String>(trustStores)); restartBrokerInManagementMode(); @@ -265,7 +273,7 @@ public class PortRestTest extends QpidRestTestCase attributes.put(Port.TRANSPORTS, Collections.singleton(Transport.TCP)); responseCode = getRestTestHelper().submitRequest("/rest/port/" + portName, "PUT", attributes); - assertEquals("Should not be able to change transport to SSL without reseting of attributes for need/want client auth", 409, responseCode); + assertEquals("Should not be able to change transport to TCP without reseting of attributes for need/want client auth", 409, responseCode); attributes = new HashMap<String, Object>(); attributes.put(Port.NAME, portName); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/TrustStoreRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/TrustStoreRestTest.java new file mode 100644 index 0000000000..87e7367235 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/TrustStoreRestTest.java @@ -0,0 +1,261 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +package org.apache.qpid.systest.rest; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.adapter.AbstractKeyStoreAdapter; +import org.apache.qpid.test.utils.TestBrokerConfiguration; +import org.apache.qpid.test.utils.TestSSLConstants; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; + +public class TrustStoreRestTest extends QpidRestTestCase +{ + @Override + public void setUp() throws Exception + { + // not calling super.setUp() to avoid broker start-up until + // after any necessary configuration + } + + public void testGet() throws Exception + { + super.setUp(); + + //verify existence of the default trust store used by the systests + List<Map<String, Object>> trustStores = assertNumberOfTrustStores(1); + + Map<String, Object> truststore = trustStores.get(0); + assertTrustStoreAttributes(truststore, TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE, + System.getProperty(QPID_HOME) + "/../" + TestSSLConstants.BROKER_TRUSTSTORE, false); + } + + public void testCreate() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfTrustStores(1); + createTrustStore(name, true); + assertNumberOfTrustStores(2); + + List<Map<String, Object>> trustStores = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details cannot be null", trustStores); + + assertTrustStoreAttributes(trustStores.get(0), name, TestSSLConstants.TRUSTSTORE, true); + } + + public void testDelete() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfTrustStores(1); + createTrustStore(name, false); + assertNumberOfTrustStores(2); + + int responseCode = getRestTestHelper().submitRequest("/rest/truststore/" + name , "DELETE", null); + assertEquals("Unexpected response code for provider deletion", 200, responseCode); + + List<Map<String, Object>> trustStore = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details should not be null", trustStore); + assertTrue("details should be empty as the truststore no longer exists", trustStore.isEmpty()); + + //check only the default systests trust store remains + List<Map<String, Object>> trustStores = assertNumberOfTrustStores(1); + Map<String, Object> truststore = trustStores.get(0); + assertTrustStoreAttributes(truststore, TestBrokerConfiguration.ENTRY_NAME_SSL_TRUSTSTORE, + System.getProperty(QPID_HOME) + "/../" + TestSSLConstants.BROKER_TRUSTSTORE, false); + } + + public void testDeleteFailsWhenTrustStoreInUse() throws Exception + { + String name = "testDeleteFailsWhenTrustStoreInUse"; + + //add a new trust store config to use + Map<String, Object> sslTrustStoreAttributes = new HashMap<String, Object>(); + sslTrustStoreAttributes.put(TrustStore.NAME, name); + sslTrustStoreAttributes.put(TrustStore.PATH, TestSSLConstants.TRUSTSTORE); + sslTrustStoreAttributes.put(TrustStore.PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); + getBrokerConfiguration().addTrustStoreConfiguration(sslTrustStoreAttributes); + + //add the SSL port using it + Map<String, Object> sslPortAttributes = new HashMap<String, Object>(); + sslPortAttributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); + sslPortAttributes.put(Port.PORT, DEFAULT_SSL_PORT); + sslPortAttributes.put(Port.NAME, TestBrokerConfiguration.ENTRY_NAME_SSL_PORT); + sslPortAttributes.put(Port.KEY_STORE, TestBrokerConfiguration.ENTRY_NAME_SSL_KEYSTORE); + sslPortAttributes.put(Port.TRUST_STORES, Collections.singleton(name)); + getBrokerConfiguration().addPortConfiguration(sslPortAttributes); + + super.setUp(); + + //verify the truststore is there + assertNumberOfTrustStores(2); + + List<Map<String, Object>> trustStore = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details should not be null", trustStore); + assertTrustStoreAttributes(trustStore.get(0), name, TestSSLConstants.TRUSTSTORE, false); + + //try to delete it, which should fail as it is in use + int responseCode = getRestTestHelper().submitRequest("/rest/truststore/" + name , "DELETE", null); + assertEquals("Unexpected response code for provider deletion", 409, responseCode); + + //check its still there + assertNumberOfTrustStores(2); + trustStore = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details should not be null", trustStore); + assertTrustStoreAttributes(trustStore.get(0), name, TestSSLConstants.TRUSTSTORE, false); + } + + public void testUpdateWithGoodPathSucceeds() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfTrustStores(1); + createTrustStore(name, false); + assertNumberOfTrustStores(2); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(TrustStore.NAME, name); + attributes.put(TrustStore.PATH, TestSSLConstants.TRUSTSTORE); + + int responseCode = getRestTestHelper().submitRequest("/rest/truststore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for truststore update", 200, responseCode); + + List<Map<String, Object>> trustStore = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details should not be null", trustStore); + + assertTrustStoreAttributes(trustStore.get(0), name, TestSSLConstants.TRUSTSTORE, false); + } + + public void testUpdateWithNonExistentPathFails() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfTrustStores(1); + createTrustStore(name, false); + assertNumberOfTrustStores(2); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(TrustStore.NAME, name); + attributes.put(TrustStore.PATH, "does.not.exist"); + + int responseCode = getRestTestHelper().submitRequest("/rest/truststore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for trust store update", 409, responseCode); + + List<Map<String, Object>> trustStore = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details should not be null", trustStore); + + //verify the details remain unchanged + assertTrustStoreAttributes(trustStore.get(0), name, TestSSLConstants.TRUSTSTORE, false); + } + + public void testUpdatePeersOnly() throws Exception + { + super.setUp(); + + String name = getTestName(); + + assertNumberOfTrustStores(1); + createTrustStore(name, false); + assertNumberOfTrustStores(2); + + //update the peersOnly attribute from false to true + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(TrustStore.NAME, name); + attributes.put(TrustStore.PEERS_ONLY, true); + + int responseCode = getRestTestHelper().submitRequest("/rest/truststore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for trust store update", 200, responseCode); + + List<Map<String, Object>> trustStore = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details should not be null", trustStore); + + assertTrustStoreAttributes(trustStore.get(0), name, TestSSLConstants.TRUSTSTORE, true); + + //Update peersOnly to clear it (i.e go from from true to null, which will default to false) + attributes = new HashMap<String, Object>(); + attributes.put(TrustStore.NAME, name); + attributes.put(TrustStore.PEERS_ONLY, null); + + responseCode = getRestTestHelper().submitRequest("/rest/truststore/" + name , "PUT", attributes); + assertEquals("Unexpected response code for trust store update", 200, responseCode); + + trustStore = getRestTestHelper().getJsonAsList("/rest/truststore/" + name); + assertNotNull("details should not be null", trustStore); + + assertTrustStoreAttributes(trustStore.get(0), name, TestSSLConstants.TRUSTSTORE, false); + } + + private List<Map<String, Object>> assertNumberOfTrustStores(int numberOfTrustStores) throws IOException, + JsonParseException, JsonMappingException + { + List<Map<String, Object>> trustStores = getRestTestHelper().getJsonAsList("/rest/truststore"); + assertNotNull("trust stores should not be null", trustStores); + assertEquals("Unexpected number of trust stores", numberOfTrustStores, trustStores.size()); + + return trustStores; + } + + private void createTrustStore(String name, boolean peersOnly) throws IOException, JsonGenerationException, JsonMappingException + { + Map<String, Object> trustStoreAttributes = new HashMap<String, Object>(); + trustStoreAttributes.put(TrustStore.NAME, name); + //deliberately using the client trust store to differentiate from the one we are already for broker + trustStoreAttributes.put(TrustStore.PATH, TestSSLConstants.TRUSTSTORE); + trustStoreAttributes.put(TrustStore.PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); + trustStoreAttributes.put(TrustStore.PEERS_ONLY, peersOnly); + + int responseCode = getRestTestHelper().submitRequest("/rest/truststore/" + name, "PUT", trustStoreAttributes); + assertEquals("Unexpected response code", 201, responseCode); + } + + private void assertTrustStoreAttributes(Map<String, Object> truststore, String name, String path, boolean peersOnly) + { + assertEquals("default systests trust store is missing", + name, truststore.get(TrustStore.NAME)); + assertEquals("unexpected path to trust store", + path, truststore.get(TrustStore.PATH)); + assertEquals("unexpected (dummy) password of default systests trust store", + AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK, truststore.get(TrustStore.PASSWORD)); + assertEquals("unexpected type of default systests trust store", + java.security.KeyStore.getDefaultType(), truststore.get(TrustStore.TYPE)); + assertEquals("unexpected peersOnly value", + peersOnly, truststore.get(TrustStore.PEERS_ONLY)); + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java index 6e6e3271f0..4004a43fde 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java @@ -31,8 +31,6 @@ import org.apache.qpid.management.common.mbeans.ManagedExchange; import org.apache.qpid.management.common.mbeans.ManagedQueue; import org.apache.qpid.management.common.mbeans.ServerInformation; import org.apache.qpid.management.common.mbeans.UserManagement; -import org.apache.qpid.server.model.Plugin; -import org.apache.qpid.server.plugin.PluginFactory; import javax.management.InstanceNotFoundException; import javax.management.JMException; @@ -47,9 +45,7 @@ import javax.management.ObjectName; import javax.management.remote.JMXConnector; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; /** diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java index db10bfb7e7..44a46fc8b2 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java @@ -31,8 +31,10 @@ import java.util.UUID; import org.apache.qpid.server.configuration.ConfigurationEntry; import org.apache.qpid.server.configuration.store.MemoryConfigurationEntryStore; import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.Plugin; import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.plugin.PluginFactory; @@ -52,6 +54,8 @@ public class TestBrokerConfiguration public static final String ENTRY_NAME_JMX_MANAGEMENT = "MANAGEMENT-JMX"; public static final String MANAGEMENT_JMX_PLUGIN_TYPE = "MANAGEMENT-JMX"; public static final String ENTRY_NAME_ANONYMOUS_PROVIDER = "anonymous"; + public static final String ENTRY_NAME_SSL_KEYSTORE = "systestsKeyStore"; + public static final String ENTRY_NAME_SSL_TRUSTSTORE = "systestsTrustStore"; private MemoryConfigurationEntryStore _store; private boolean _saved; @@ -144,6 +148,18 @@ public class TestBrokerConfiguration return addObjectConfiguration(name, AuthenticationProvider.class.getSimpleName(), attributes); } + public UUID addTrustStoreConfiguration(Map<String, Object> attributes) + { + String name = (String) attributes.get(TrustStore.NAME); + return addObjectConfiguration(name, TrustStore.class.getSimpleName(), attributes); + } + + public UUID addKeyStoreConfiguration(Map<String, Object> attributes) + { + String name = (String) attributes.get(KeyStore.NAME); + return addObjectConfiguration(name, KeyStore.class.getSimpleName(), attributes); + } + private boolean setObjectAttributes(ConfigurationEntry entry, Map<String, Object> attributes) { Map<String, Object> newAttributes = new HashMap<String, Object>(entry.getAttributes()); |