TLS tests for Restconf Northbound interface 78/52878/15
authormangel <m.angel890@gmail.com>
Mon, 6 Mar 2017 12:18:18 +0000 (13:18 +0100)
committerJamo Luhrsen <jluhrsen@redhat.com>
Fri, 24 Mar 2017 00:20:11 +0000 (00:20 +0000)
Support for configuring, activating and executing HTTPS requests towards
northbound interface provided by Jetty.
Note for activating certaing configurations it is required to restart
ODL completely by means of Karaf command: shutdown -r

For reloading certificate information (keystore/trustore) it is only
required to restart jetty bundle. Note this operation is not supported
officially for production environments.

These tests are based on openssl/keytool external commands.

Change-Id: I21b2d2f60d65a04342ef6d1de882c2b59915f40b
Signed-off-by: Miguel Angel Munoz Gonzalez <miguel.angel.munoz.gonzalez@ericsson.com>
csit/libraries/KarafKeywords.robot
csit/libraries/Utils.robot
csit/suites/aaa/tls/010__aaa_restconf_tls.robot [new file with mode: 0644]
csit/testplans/aaa-tls.txt [new file with mode: 0644]
csit/variables/Variables.robot

index 8bbbd72fb32cb5444e4004439382cfdbd8884bc5..4c8e8d412836226112d550e34459f4c07d13294d 100644 (file)
@@ -202,3 +202,20 @@ Wait For Karaf Log
     Write    log:tail
     Read Until    ${message}
     Close Connection
+
+Restart Bundle
+    [Arguments]    ${bundle_id}
+    [Documentation]    Restarts bundle passed as argument. Note this operation is only for testing and not production environments
+    # TODO: prepare this for cluster environment and multiple controllers
+    Safe_Issue_Command_On_Karaf_Console    bundle:restart -f $(bundle:id ${bundle_id})
+
+Restart Karaf
+    [Documentation]    Restarts Karaf and polls log to detect when Karaf is up and running again
+    # TODO: prepare this for cluster environment and multiple controllers
+    Safe_Issue_Command_On_Karaf_Console    log:clear
+    Issue_Command_On_Karaf_Console    shutdown -r -f
+    Run Keyword And Return Status    Wait Until Keyword Succeeds    4x    60s    Wait For Karaf Log    Karaf started in
+
+Restart Jetty
+    [Documentation]    Restarts jetty bundle (to reload certificates or key/truststore information)
+    Restart Bundle    OPS4J Pax Web - Jetty
index c3d6b6b1811251b2a13ddf5b23cd7ee8209a65d9..d5d866a2784fefff7e3562bc5b33ddea5e07e006 100644 (file)
@@ -236,6 +236,15 @@ Verify File Exists On Remote System
     SSHLibrary.File Should Exist    ${file}
     Close Connection
 
+Copy File To Remote System
+    [Arguments]    ${system}    ${source}    ${destination}    ${user}=${TOOLS_SYSTEM_USER}    ${password}=${TOOLS_SYSTEM_PASSWORD}    ${prompt}=${DEFAULT_LINUX_PROMPT}
+    ...    ${prompt_timeout}=5s
+    [Documentation]    Simplifies copy file operations to remote system
+    ${conn_id}=    Open Connection    ${system}    prompt=${prompt}    timeout=${prompt_timeout}
+    Flexible SSH Login    ${user}    ${password}
+    SSHLibrary.Put File    ${source}    ${destination}
+    Close Connection
+
 Check Karaf Log File Does Not Have Messages
     [Arguments]    ${ip}    ${message}    ${user}=${ODL_SYSTEM_USER}    ${password}=${ODL_SYSTEM_PASSWORD}    ${prompt}=${ODL_SYSTEM_PROMPT}    ${log_file}=${WORKSPACE}/${BUNDLEFOLDER}/data/log/karaf.log
     [Documentation]    Fails if the provided ${message} is found in the karaf.log file. Uses grep to search. The
diff --git a/csit/suites/aaa/tls/010__aaa_restconf_tls.robot b/csit/suites/aaa/tls/010__aaa_restconf_tls.robot
new file mode 100644 (file)
index 0000000..b56f3f0
--- /dev/null
@@ -0,0 +1,164 @@
+*** Settings ***
+Documentation     Test suite for Securing RESTCONF communication.
+...               Note this suite requires PycURLLibrary to handle client certificates. While Requests library is able
+...               to handle server certificates well, it lacks capabilities to deal with client certificates.
+Suite Setup       Init Suite
+Suite Teardown    Cleanup Suite
+Library           OperatingSystem
+Library           RequestsLibrary
+Library           PycURLLibrary
+Library           SSHLibrary
+Resource          ../../../libraries/ClusterManagement.robot
+Resource          ../../../variables/Variables.robot
+Resource          ../../../libraries/Utils.robot
+Resource          ../../../libraries/KarafKeywords.robot
+
+*** Variables ***
+${RESTCONF_MONITORING_URI}    /restconf/operational/ietf-restconf-monitoring:restconf-state
+${RESTCONF_MONITORING_URL}    https://${ODL_SYSTEM_IP}:${RESTCONFPORT_TLS}${RESTCONF_MONITORING_URI}
+
+*** Test Cases ***
+Basic Unsecure Restconf Request
+    [Documentation]    Tests a basic HTTP request, just to ensure that system is working fine with normal, unsecure reqs
+    Create Session    session    http://${ODL_SYSTEM_IP}:${RESTCONFPORT}    auth=${AUTH}    headers=${HEADERS}
+    ${resp}    RequestsLibrary.Get Request    session    ${RESTCONF_MONITORING_URI}
+    Should Contain    ${ALLOWED_STATUS_CODES}    ${resp.status_code}
+    Delete All Sessions
+
+TLS on Restconf without Server Cert
+    [Documentation]    Tests an HTTPS request towards secure port with ODL secure config deactivated
+    PycURLLibrary.Set Url    ${RESTCONF_MONITORING_URL}
+    PycURLLibrary.Add Header    "Content-Type:application/json"
+    PycURLLibrary.Add Header    Authorization:Basic YWRtaW46YWRtaW4=
+    PycURLLibrary.Request Method    GET
+    Run Keyword And Expect Error    error: (7, 'Failed *${RESTCONFPORT_TLS}* Connection refused')    PycURLLibrary.Perform
+    PycURLLibrary.Log Response
+
+Activate TLS and Generate Server Certificate
+    [Documentation]    Generates a server certificate, self-signed and activates ODL secure configuration.
+    Generate Server Self-Signed Certificate
+    Enable TLS in ODL
+    # Check ODL was restarted properly
+    Create Session    session    http://${ODL_SYSTEM_IP}:${RESTCONFPORT}    auth=${AUTH}    headers=${HEADERS}
+    ${resp}    RequestsLibrary.Get Request    session    ${RESTCONF_MONITORING_URI}
+    Delete All Sessions
+    Should Contain    ${ALLOWED_STATUS_CODES}    ${resp.status_code}
+
+TLS on Restconf with Server Cert (Self-signed) (insecure)
+    [Documentation]    Tests HTTPS request. Server certificate is self-signed, thus communication is insecure
+    PycURLLibrary.Set Url    ${RESTCONF_MONITORING_URL}
+    PycURLLibrary.Add Header    "Content-Type:application/json"
+    PycURLLibrary.Add Header    Authorization:Basic YWRtaW46YWRtaW4=
+    PycURLLibrary.Request Method    GET
+    PycURLLibrary.Perform
+    PycURLLibrary.Log Response
+    PycURLLibrary.Response Status Should Contain    200
+    ${resp}    PycURLLibrary.Response
+    Should Contain    ${resp}    "restconf-state":{"capabilities":{"capability":["urn:ietf:params:restconf:capability:depth
+
+Activate Client Authentication and Generate Client Certificate
+    [Documentation]    Generates a client certificate and imports it into ODL truststore.
+    ...    Changes ODL config to require client authentication
+    Generate Client Self-Signed Certificate
+    Enable Client TLS Authentication in ODL
+    # Check ODL was restarted properly
+    Create Session    session    http://${ODL_SYSTEM_IP}:${RESTCONFPORT}    auth=${AUTH}    headers=${HEADERS}
+    ${resp}    RequestsLibrary.Get Request    session    ${RESTCONF_MONITORING_URI}
+    Delete All Sessions
+    Should Contain    ${ALLOWED_STATUS_CODES}    ${resp.status_code}
+
+TLS on Restconf with Server & Client Certs (Self-signed)
+    [Documentation]    Test HTTPS request with ODL TLS config and client authentication by using certificate
+    PycURLLibrary.Set Url    ${RESTCONF_MONITORING_URL}
+    PycURLLibrary.Add Header    "Content-Type:application/json"
+    PycURLLibrary.Add Header    Authorization:Basic YWRtaW46YWRtaW4=
+    PycURLLibrary.Client Certificate File    ${USER_HOME}/clientcert.pem
+    PycURLLibrary.Private Key File    ${USER_HOME}/clientkey.pem
+    PycURLLibrary.Request Method    GET
+    PycURLLibrary.Perform
+    PycURLLibrary.Log Response
+    PycURLLibrary.Response Status Should Contain    200
+    ${resp}    PycURLLibrary.Response
+    Should Contain    ${resp}    "restconf-state":{"capabilities":{"capability":["urn:ietf:params:restconf:capability:depth
+
+TLS on Restconf with Server & Client Certs (CA signed)
+    [Documentation]    Tests HTTPS request with ODL TLS config and client authentication by using CA signed certificates
+    [Tags]    exclude
+
+Restconf HTTPS/TLS Jolokia with server and client certificates CA signed
+    [Documentation]    Tests HTTPS request with ODL TLS config and client authentication by using CA signed certificates for Jolokia
+    [Tags]    exclude
+
+*** Keywords ***
+Log Certificates in Keystore
+    [Documentation]    Shows content of keystore
+    ${output}    Run Command On Remote System    ${ODL_SYSTEM_IP}    ${JAVA_HOME}/bin/keytool -list -storepass 123456 -keystore ${KEYSTORE_PATH}
+    log    ${output}
+
+Clean Up Certificates In Server
+    [Documentation]    Cleans keystore content (only for private keys and trusted certificates)
+    Log Certificates in Keystore
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    ${JAVA_HOME}/bin/keytool -list -keystore ${KEYSTORE_PATH} -storepass 123456|egrep -e "(trustedCertEntry|PrivateKeyEntry)"|cut -d"," -f1|xargs -I[] ${JAVA_HOME}/bin/keytool -delete -alias [] -keystore ${KEYSTORE_PATH} -storepass 123456
+    Log Certificates in Keystore
+
+Generate Server Self-Signed Certificate
+    [Documentation]    Generates a self-signed certificate, stores it into keystore and restarts jetty to load changes
+    ${KEYSTORE_DIR}=    Split Path    ${KEYSTORE_PATH}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    mkdir -p ${KEYSTORE_DIR[0]}
+    Log Certificates in Keystore
+    # Generate with openssl
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    openssl req -x509 -newkey rsa:4096 -passout pass:myPass -keyout serverkey.pem -out servercert.pem -days 365 -subj "/C=ES/ST=Madrid/L=Madrid/O=OpenDayLight/OU=AAA/CN=OpenDayLight/emailAddress=unknown@unknown.com"
+    # Convert to pkcs12 (including public and private key together)
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    openssl pkcs12 -export -in servercert.pem -inkey serverkey.pem -out server.p12 -name odl -passin pass:myPass -passout pass:myPass
+    # Import Certifcate into keystore
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    ${JAVA_HOME}/bin/keytool -importkeystore -deststorepass 123456 -destkeypass myPass -destkeystore ${KEYSTORE_PATH} -srckeystore server.p12 -srcstoretype PKCS12 -srcstorepass myPass -alias odl
+    Log Certificates in Keystore
+    Restart Jetty
+
+Generate Client Self-Signed Certificate
+    [Documentation]    Generates a client self-signed certificate, stores it into the keystore (as trusted cert) and
+    ...    restarts jettty to load changes
+    ${KEYSTORE_DIR}=    Split Path    ${KEYSTORE_PATH}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    mkdir -p ${KEYSTORE_DIR[0]}
+    Log Certificates in Keystore
+    # Generate with openssl
+    # Note -nodes is used to avoid passphrase in private key. Also -passout pass:myPass is skipped. This is due to a
+    # limitation in pycurl library that does not support key pem files with passphrase in automatic mode (it asks for it)
+    Run    openssl req -x509 -newkey rsa:4096 -nodes -keyout ${USER_HOME}/clientkey.pem -out ${USER_HOME}/clientcert.pem -days 365 -subj "/C=ES/ST=Madrid/L=Madrid/O=OpenDayLight/OU=AAA/CN=MiguelAngelMunoz/emailAddress=myemail@unknown.com"
+    # Import client's cert as trusted
+    Copy File To Remote System    ${ODL_SYSTEM_IP}    ${USER_HOME}/clientcert.pem    .
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    ${JAVA_HOME}/bin/keytool -import -trustcacerts -file clientcert.pem -keystore ${KEYSTORE_PATH} -storepass 123456 -noprompt
+    Log Certificates in Keystore
+    Restart Jetty
+
+Disable TLS in ODL
+    [Documentation]    Remove TLS configuration in custom.properties
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    sed -i '/org.osgi.service.http.secure.enabled=/d' ${CUSTOMPROP}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    sed -i '/org.ops4j.pax.web.ssl.keystore=/d' ${CUSTOMPROP}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    sed -i '/org.ops4j.pax.web.ssl.password=/d' ${CUSTOMPROP}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    sed -i '/org.ops4j.pax.web.ssl.keypassword=/d' ${CUSTOMPROP}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    sed -i '/org.ops4j.pax.web.ssl.clientauthneeded=/d' ${CUSTOMPROP}
+    Restart Karaf
+
+Enable TLS in ODL
+    [Documentation]    Add new secure configuration in custom.properties
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    echo "org.osgi.service.http.secure.enabled=true">> ${CUSTOMPROP}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    echo "org.ops4j.pax.web.ssl.keystore=${KEYSTORE_RELATIVE_PATH}">> ${CUSTOMPROP}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    echo "org.ops4j.pax.web.ssl.password=myPass">> ${CUSTOMPROP}
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    echo "org.ops4j.pax.web.ssl.keypassword=123456">> ${CUSTOMPROP}
+    Restart Karaf
+
+Enable Client TLS Authentication in ODL
+    [Documentation]    Add custom.properties configuration to enable client auth
+    Run Command On Remote System    ${ODL_SYSTEM_IP}    echo "org.ops4j.pax.web.ssl.clientauthneeded=true">> ${CUSTOMPROP}
+    Restart Karaf
+
+Init Suite
+    [Documentation]    Cleans TLS configuration and restart Karaf system to reload
+    ClusterManagement_Setup
+    Clean Up Certificates In Server
+    Disable TLS in ODL
+
+Cleanup Suite
+    [Documentation]    Deletes pending sessions in case there were any
+    Delete All Sessions
diff --git a/csit/testplans/aaa-tls.txt b/csit/testplans/aaa-tls.txt
new file mode 100644 (file)
index 0000000..5caaaf2
--- /dev/null
@@ -0,0 +1,3 @@
+# Place the suites in run order:
+integration/test/csit/suites/aaa/tls
+
index 5f6d6637e22c235bd44b33e8428bb0c8bc58f958..3d11fae10149a5335dde7b2d158504253f05ed00 100644 (file)
@@ -54,6 +54,7 @@ ${CREATE_PATHPOLICY_TOPOLOGY_FILE}    topo-3sw-2host_multipath.py    # A shortha
 ${CREATE_PATHPOLICY_TOPOLOGY_FILE_PATH}    MininetTopo/${CREATE_PATHPOLICY_TOPOLOGY_FILE}    # A shorthand. FIXME: Find who uses this and eliminate, or at least add a good description.
 ${CREATE_VLAN_TOPOLOGY_FILE}    vlan_vtn_test.py    # A shorthand. FIXME: Find who uses this and eliminate, or at least add a good description.
 ${CREATE_VLAN_TOPOLOGY_FILE_PATH}    MininetTopo/${CREATE_VLAN_TOPOLOGY_FILE}    # A shorthand. FIXME: Find who uses this and eliminate, or at least add a good description.
+${CUSTOMPROP}     /tmp/${BUNDLEFOLDER}/etc/custom.properties    # Full path of custom.properties file
 ${DEFAULT_LINUX_PROMPT}    >    # Generic *_SYSTEM prompt for SSHLibrary.Read_Unti_Prompt. Current value is there for historic reasons. FIXME: Add -v to releng/builder and change this value to more common "$" (without quotes, with backslash). TODO: Replace usage with the strict version.
 ${DEFAULT_LINUX_PROMPT_STRICT}    ]>    # A more strict prompt substring, this only matches Bash prompt, not Mininet prompt.
 ${DEFAULT_BGPCEP_LOG_LEVEL}    ${DEFAULT_ODL_LOG_LEVEL}    # Fallback Karaf log level specific to org.opendaylight.bgpcep.
@@ -91,6 +92,8 @@ ${KARAF_PROMPT}    opendaylight-user    # Simple and readable Karaf prompt subst
 ${KARAF_SHELL_PORT}    8101    # ODL provides SSH access to Karaf consoleon this port.
 ${KARAF_USER}     karaf    # User name to authenticate to Karaf SSH console.
 ${KEYFILE_PASS}    any    # Implementation detail related to SSHLibrary.Login_With_Public_Key. TODO: Hide in SSHKeywords.
+${KEYSTORE_PATH}    /tmp/${BUNDLEFOLDER}/configuration/ssl/.keystore    # Full path of keystore for TLS communication
+${KEYSTORE_RELATIVE_PATH}    configuration/ssl/.keystore    # Relative path of keystore for TLS communication
 ${LFM_RPC_API}    /restconf/operations/odl-mappingservice    # FIXME: Move to a separate LispFlowMapping-related Resource and add description.
 ${LFM_RPC_API_LI}    /restconf/operations/lfm-mapping-database    # FIXME: Move to a separate LispFlowMapping-related Resource and add description.
 ${LFM_SB_RPC_API}    /restconf/operations/odl-lisp-sb    # FIXME: Move to a separate LispFlowMapping-related Resource and add description.
@@ -140,6 +143,7 @@ ${PROTOCOL_LOG_LEVEL}    ${DEFAULT_PROTOCOL_LOG_LEVEL}    # Some suites temporar
 ${PWD}            ${ODL_RESTCONF_PASSWORD}    # Deprecated. FIXME: Eradicate.
 ${REGISTER_TENANT_URI}    /restconf/operations/nemo-intent:register-user    # FIXME: Move to a separate Nemo-related Resource and add description.
 ${RESTCONFPORT}    8181    # Primary port for ODL RESTCONF, although 8080 should also work.
+${RESTCONFPORT_TLS}    8443    # Port for ODL RESTCONF Secure (TLS) operations
 ${RESTPORT}       8282    # Deprecated. Restconf port used by AD-SAL services. FIXME: Eradicate.
 ${REVOKE_TOKEN_API}    /oauth2/revoke    # FIXME: Move to a separate AAA-related Resource and add description.
 ${SCOPE}          sdn    # Scope, used for some types of HTTP requests agains ODL RESTCONF. TODO: Migrate most suites to TemplatedRequests or AuthStandalone, then chose a more descriptive name.