Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/sdk/proxies/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Service Proxies
Relational Database Service RDS V3 (RDS) <rds_v3>
Volume Backup Service (VBS) <volume_backup>
Virtual Private Cloud (VPC) <vpc>
Web Application Firewall (WAF) <waf>

.. _service-proxies:

Expand Down
22 changes: 22 additions & 0 deletions doc/source/sdk/proxies/waf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Web Application Firewall API
============================

.. automodule:: otcextensions.sdk.waf.v1._proxy

The WAF Service Class
---------------------

The waf high-level interface is available through the ``waf``
member of a :class:`~openstack.connection.Connection` object. The
``waf`` member will only be added if the
``otcextensions.sdk.register_otc_extensions(conn)`` method is called.

Certificate Operations
^^^^^^^^^^^^^^^^^^^^^^

.. autoclass:: otcextensions.sdk.waf.v1._proxy.Proxy
:noindex:
:members: certificates, create_certificate, get_certificate,
delete_certificate, update_certificate, find_certificate


2 changes: 1 addition & 1 deletion otcextensions/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
},
'waf': {
'service_type': 'waf',
'append_project_id': True,
'set_endpoint_override': True
}
}

Expand Down
68 changes: 68 additions & 0 deletions otcextensions/sdk/waf/v1/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import exceptions
from openstack import resource


class Resource(resource.Resource):

@classmethod
def find(cls, session, name_or_id, ignore_missing=True, **params):
"""Find a resource by its name or id.

:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param name_or_id: This resource's identifier, if needed by
the request. The default is ``None``.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be
raised when the resource does not exist.
When set to ``True``, None will be returned when
attempting to find a nonexistent resource.
:param dict params: Any additional parameters to be passed into
underlying methods, such as to
:meth:`~openstack.resource.Resource.existing`
in order to pass on URI parameters.

:return: The :class:`Resource` object matching the given name or id
or None if nothing matches.
:raises: :class:`openstack.exceptions.DuplicateResource` if more
than one resource is found for this request.
:raises: :class:`openstack.exceptions.ResourceNotFound` if nothing
is found and ignore_missing is ``False``.
"""
session = cls._get_session(session)
# Try to short-circuit by looking directly for a matching ID.
try:
match = cls.existing(
id=name_or_id,
connection=session._get_connection(),
**params)
return match.fetch(session, **params)
except exceptions.SDKException:
# WAF will return 400 when we try to do GET with name
pass

if ('name' in cls._query_mapping._mapping.keys()
and 'name' not in params):
params['name'] = name_or_id

data = cls.list(session, **params)

result = cls._get_one_match(name_or_id, data)
if result is not None:
return result

if ignore_missing:
return None
raise exceptions.ResourceNotFound(
"No %s found for %s" % (cls.__name__, name_or_id))
98 changes: 98 additions & 0 deletions otcextensions/sdk/waf/v1/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,105 @@
# under the License.
from openstack import proxy

from otcextensions.sdk.waf.v1 import certificate as _cert


class Proxy(proxy.Proxy):

skip_discovery = True

def __init__(self, session, *args, **kwargs):
super(Proxy, self).__init__(session=session, *args, **kwargs)
self.endpoint_override = \
'%s/%s/%s' % (
self.get_endpoint(),
'v1',
'%(project_id)s')
self.additional_headers = {
'x-request-source-type': 'ApiCall',
'content-type': 'application/json'
}

# ======== Certificates ========
def certificates(self, **query):
"""Retrieve a generator of certificates

:param dict query: Optional query parameters to be sent to limit the
resources being returned.
* `limit`: pagination limit
* `offset`: pagination offset

:returns: A generator of certificate
:class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
instances
"""
return self._list(_cert.Certificate, **query)

def create_certificate(self, **attrs):
"""Upload certificate from attributes

:param dict attrs: Keyword arguments which will be used to create
a :class:`~otcextensions.sdk.waf.v1.certificate.Certificate`,
comprised of the properties on the Certificate class.
:returns: The results of certificate creation
:rtype: :class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
"""
print(attrs)
return self._create(_cert.Certificate, prepend_key=False, **attrs)

def get_certificate(self, certificate):
"""Get a certificate

:param certificate: The value can be the ID of a certificate
or a :class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
instance.
:returns: Certificate instance
:rtype: :class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
"""
return self._get(_cert.Certificate, certificate)

def delete_certificate(self, certificate, ignore_missing=True):
"""Delete a certificate

:param certificate: The value can be the ID of a certificate
or a :class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
instance.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
the certificate does not exist.
When set to ``True``, no exception will be set when attempting to
delete a nonexistent certificate.

:returns: Certificate been deleted
:rtype: :class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
"""
return self._delete(_cert.Certificate, certificate,
ignore_missing=ignore_missing)

def update_certificate(self, certificate, **attrs):
"""Update certificate attributes

:param certificate: The id or an instance of
:class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
:param dict attrs: attributes for update on
:class:`~otcextensions.sdk.waf.v1.certificate.Certificate`

:rtype: :class:`~otcextensions.sdk.waf.v1.certificate.Certificate`
"""
return self._update(_cert.Certificate, certificate, **attrs)

def find_certificate(self, name_or_id, ignore_missing=True, **attrs):
"""Find a single certificate

:param name_or_id: The name or ID of a certificate
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised
when the certificate does not exist.
When set to ``True``, no exception will be set when attempting
to delete a nonexistent certificate.

:returns: ``None``
"""
return self._find(_cert.Certificate, name_or_id,
ignore_missing=ignore_missing,
**attrs)
40 changes: 40 additions & 0 deletions otcextensions/sdk/waf/v1/certificate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import resource

from otcextensions.sdk.waf.v1 import _base


class Certificate(_base.Resource):
"""WAF Certificate Resource"""
resources_key = 'items'
base_path = '/waf/certificate'

# capabilities
allow_create = True
allow_list = True
allow_fetch = True
allow_delete = True
allow_commit = True

_query_mapping = resource.QueryParameters(
'limit', 'offset')

#: Properties
#: Specifies the certificate content.
content = resource.Body('content')
#: Certificate expiration time
expire_time = resource.Body('expireTime')
#: Specifies the private key content.
key = resource.Body('key')
#: Certificat uploading timestamp
timestamp = resource.Body('timestamp')
21 changes: 21 additions & 0 deletions otcextensions/tests/functional/sdk/waf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import openstack
from otcextensions.tests.functional import base


class TestWaf(base.BaseFunctionalTest):

def setUp(self):
super(TestWaf, self).setUp()
openstack.enable_logging(debug=True)
self.client = self.conn.waf
114 changes: 114 additions & 0 deletions otcextensions/tests/functional/sdk/waf/v1/test_certificate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid

# from openstack import resource

from otcextensions.tests.functional.sdk.waf import TestWaf


class TestCertificate(TestWaf):

_PRIVATE_KEY = """-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDrvw+CfkRMtN6I
KQK+YNKhjdWqUCnTI7YqZLDhZkoIqcvK1F2mjkcoGXOAjCjvGXf/xX35j0dGLgHK
e3AwNaQDPRWec6DuTqh9kBq9Qy7rUs6Na85wwSN8FG7z9XRuWR9NhEg24nrATUr/
k5biBtHKiP22xI9nVws+IEoYVGOOjJ2CPt9XszaS/pdN6bQLchPSLOM5WN6BHCVn
u19RnSKFqCr8AmYx2Aqo30uFTHy1EhvSX8CnRTHNvWl7qciISqiNenXjIZyCe80n
7VB+LzSbm3HeqMDsM0euq0P5uPty2A6Uuo/TlPWlls6ZhFTP+AQ9H78kPWY9nKjl
1Ja4K0sbAgMBAAECggEANJ9oceOHkWvKRLCK2T45pjBH4oWUYHoXPq1NQnMX0Yk9
YWA4K2aVAaF0w9wFgyG3RJOsBBn0efjpE26sY0aF/ucSvVToNmm+eJDDNz4Y6hSI
4M6QvWCPcDILdk9zFvKz5xTBHec+KVDXjec/BeMpz0D3CWYk8JdgfhStFXM46eeR
z1KBOq51x+I0VD3Ar4T3hfKG2IViwevC/7kghBw+D1U/c4stHFCXv4JlrhFET2I6
kquGtV38fMUdWBLRVr0wBB4orm+9rpSlTvbnDuuEJcb8rKvrLkGraUhSTqepQD6M
lTN4BxY+3NqdnP/SKVBRoXr+gQsLdgPUAhkvTB8f0QKBgQD32mpyweaMZYTqZ8xF
xOBzjCTGVHNlXMMt8rz9+kJ4krJ77R3L07qf+mo5bsOB2ZybHhTy7+G6QO8TXyrI
60nbpoFR0eyWy6kdn4NtY/9BCcj13cV1D495zLr2HAveWDVVGJpLorkG5d674dtl
wD+B5EQIliCVR5GWMeciFGrewwKBgQDzfsU+EXlKAw6KMInyRP/+nWNk0PFir01H
Q4C/SrTM/Y8bCJ3/pWVAQsxEbQk1pOdWcdzHFf8BRncMA+OUDTxSCHJYiaqL+2pN
nNB3/bShocMKvDodJxXWMhdM2fMLFMtYCNsjr0DM8Cqvw7oZF8MY6oxM+uWzmI5R
nWKFMFXMyQKBgQDBK8PnKOSM69qJ7tgwUF827zUCNnOxvniIaTWPJOuFmZ/uIkIk
yCId6Ue892z82SPLacieBwQA6/bpPDTWXzszLDSCFoC0joqCAf6m1Vbt07iCl5P7
xmLmZQAaLIW7hzgZ2JD4/hwDGklcWY1rYkic7dFwd8FxV1RKoR4pW4xnjQKBgQDf
nEbU9kUVg/MhUuwL8fPJxo3VstBKWUS1sjcU9S1Op3h5UhOPBzwRpIZkPGHdwr+0
MkKXDgsuB6EiBpxDhVgk2Z7w0hQuE0gPWHhWCUaNvLkaLbuMtC0olL2zFOBPB9yp
zxA4GCSBT/lTioJnstu3EQahVzQFF49zQf6M49OXiQKBgCqOdwZjTH5gBnDSbWMM
WAFcxEzr5moG4nJzz/5sGqN5IRy1zDd/QkV2KEhjzWFbpGMgbgNTiLmz0BT6hUXl
/jS27B9AOPsdktyb88+ZuEfG6dYCmPnjBiOUrovbFk5IIAmiMAUT+W9HXN9shH0g
Ltxv392mcEGwmbfc1YJJfN2B
-----END PRIVATE KEY-----"""

_CERTIFICATE = """-----BEGIN CERTIFICATE-----
MIIDADCCAegCCQCUu4mu6VfH/zANBgkqhkiG9w0BAQsFADBCMQswCQYDVQQGEwJE
RTELMAkGA1UEBwwCUEIxDDAKBgNVBAoMA1RTSTEYMBYGA1UEAwwPbXlmYWtlLnRl
c3QuY29tMB4XDTIwMDkwMTA5Mjc1M1oXDTIxMDkwMTA5Mjc1M1owQjELMAkGA1UE
BhMCREUxCzAJBgNVBAcMAlBCMQwwCgYDVQQKDANUU0kxGDAWBgNVBAMMD215ZmFr
ZS50ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOu/D4J+
REy03ogpAr5g0qGN1apQKdMjtipksOFmSgipy8rUXaaORygZc4CMKO8Zd//FffmP
R0YuAcp7cDA1pAM9FZ5zoO5OqH2QGr1DLutSzo1rznDBI3wUbvP1dG5ZH02ESDbi
esBNSv+TluIG0cqI/bbEj2dXCz4gShhUY46MnYI+31ezNpL+l03ptAtyE9Is4zlY
3oEcJWe7X1GdIoWoKvwCZjHYCqjfS4VMfLUSG9JfwKdFMc29aXupyIhKqI16deMh
nIJ7zSftUH4vNJubcd6owOwzR66rQ/m4+3LYDpS6j9OU9aWWzpmEVM/4BD0fvyQ9
Zj2cqOXUlrgrSxsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAcNsm1y3PgC47O7qW
3X531EiXmXsKuFWrpQeuVgSI/PrtXCn3/Gr1GcFQDA3k5iyDsApohwbyUcpXhA6c
842r2Flb11tMF7lxHwHGffryBeFbvCNSNYDvN9zA/XQfqpYi4UPPXPyLH0jVD0Ek
BCqJJFFzkRbUTcvTxCUxNEYpIQC8U4RSyWXg5kTu6302YjmWaNcP3bfL4II/ddI4
WyGW6tZI2z7GTYPutWljmtfgEto2Y3FimtiGU+P/uB6SxlESzkGEvAfEduGlyxY8
uslYHnizLvYY6FaAdExE1TpM6YrM3b7aYMgv700CDsBCpFncQUx9tujpQxCmMoHZ
rNcviQ==
-----END CERTIFICATE-----"""
CERT_NAME = "SDK-" + uuid.uuid4().hex

def setUp(self):
super(TestCertificate, self).setUp()
self.cert = self.client.create_certificate(
key=self._PRIVATE_KEY,
content=self._CERTIFICATE,
name=self.CERT_NAME
)

self.addCleanup(self.conn.waf.delete_certificate, self.cert)

def test_list_certificates(self):
query = {}
certs = list(self.client.certificates(**query))
self.assertGreaterEqual(len(certs), 0)

def test_get_certificate(self):
cert = self.client.get_certificate(self.cert.id)
self.assertEqual(self.cert.name, cert.name)
self.assertEqual(self.cert.timestamp, cert.timestamp)
self.assertEqual(self.cert.expire_time, cert.expire_time)

def test_find_certificate(self):
cert = self.client.find_certificate(self.cert.name)
self.assertEqual(self.cert.name, cert.name)
self.assertEqual(self.cert.timestamp, cert.timestamp)
self.assertEqual(self.cert.expire_time, cert.expire_time)

def test_update_certificate(self):
cert2 = self.client.create_certificate(
key=self._PRIVATE_KEY,
content=self._CERTIFICATE,
name=self.CERT_NAME + "_2"
)

self.addCleanup(self.conn.waf.delete_certificate, cert2)
cert2_cmp = self.client.update_certificate(
cert2,
name=self.CERT_NAME + "_2_cp"
)
self.assertEqual(cert2.name, cert2_cmp.name)

cert2_cmp = self.client.get_certificate(cert2_cmp.id)
self.assertEqual(cert2.name, cert2_cmp.name)
self.assertEqual(cert2.id, cert2_cmp.id)
Loading