Merge pull request #192 from kevgliss/sensitive-domains
Adds ability for domains to be marked as sensitive and only be allowe…
This commit is contained in:
commit
a4bf847b56
|
@ -19,6 +19,11 @@ CertificateCreator = namedtuple('certificate', ['method', 'value'])
|
|||
CertificateCreatorNeed = partial(CertificateCreator, 'key')
|
||||
|
||||
|
||||
class SensitiveDomainPermission(Permission):
|
||||
def __init__(self):
|
||||
super(SensitiveDomainPermission, self).__init__(RoleNeed('admin'))
|
||||
|
||||
|
||||
class ViewKeyPermission(Permission):
|
||||
def __init__(self, certificate_id, owner):
|
||||
c_need = CertificateCreatorNeed(certificate_id)
|
||||
|
|
|
@ -7,16 +7,24 @@
|
|||
"""
|
||||
import base64
|
||||
from builtins import str
|
||||
|
||||
from flask import Blueprint, make_response, jsonify
|
||||
from flask.ext.restful import reqparse, Api, fields
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
from lemur.auth.service import AuthenticatedResource
|
||||
from lemur.auth.permissions import ViewKeyPermission
|
||||
from lemur.auth.permissions import AuthorityPermission
|
||||
from lemur.auth.permissions import UpdateCertificatePermission
|
||||
from lemur.auth.permissions import SensitiveDomainPermission
|
||||
|
||||
from lemur.certificates import service
|
||||
from lemur.authorities.models import Authority
|
||||
from lemur.auth.service import AuthenticatedResource
|
||||
from lemur.auth.permissions import ViewKeyPermission, AuthorityPermission, UpdateCertificatePermission
|
||||
from lemur.roles import service as role_service
|
||||
from lemur.domains import service as domain_service
|
||||
from lemur.common.utils import marshal_items, paginated_parser
|
||||
from lemur.notifications.views import notification_list
|
||||
|
||||
|
@ -63,6 +71,34 @@ def valid_authority(authority_options):
|
|||
return authority
|
||||
|
||||
|
||||
def get_domains_from_options(options):
|
||||
"""
|
||||
Retrive all domains from certificate options
|
||||
:param options:
|
||||
:return:
|
||||
"""
|
||||
domains = [options['commonName']]
|
||||
for k, v in options['extensions']['subAltNames']['names']:
|
||||
if k == 'DNSName':
|
||||
domains.append(v)
|
||||
return domains
|
||||
|
||||
|
||||
def check_sensitive_domains(domains):
|
||||
"""
|
||||
Determines if any certificates in the given certificate
|
||||
are marked as sensitive
|
||||
:param domains:
|
||||
:return:
|
||||
"""
|
||||
for domain in domains:
|
||||
domain_objs = domain_service.get_by_name(domain)
|
||||
for d in domain_objs:
|
||||
if d.sensitive:
|
||||
raise ValueError("The domain {0} has been marked as sensitive. Contact an administrator to "
|
||||
"issue this certificate".format(d.name))
|
||||
|
||||
|
||||
def pem_str(value, name):
|
||||
"""
|
||||
Used to validate that the given string is a PEM formatted string
|
||||
|
@ -338,9 +374,12 @@ class CertificatesList(AuthenticatedResource):
|
|||
|
||||
# allow "owner" roles by team DL
|
||||
roles.append(role)
|
||||
permission = AuthorityPermission(authority.id, roles)
|
||||
authority_permission = AuthorityPermission(authority.id, roles)
|
||||
|
||||
if permission.can():
|
||||
if authority_permission.can():
|
||||
# if we are not admins lets make sure we aren't issuing anything sensitive
|
||||
if not SensitiveDomainPermission().can():
|
||||
check_sensitive_domains(get_domains_from_options(args))
|
||||
return service.create(**args)
|
||||
|
||||
return dict(message="You are not authorized to use {0}".format(args['authority'].name)), 403
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from sqlalchemy import Column, Integer, String, Boolean
|
||||
|
||||
from lemur.database import db
|
||||
|
||||
|
@ -16,11 +16,4 @@ class Domain(db.Model):
|
|||
__tablename__ = 'domains'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(256))
|
||||
|
||||
def as_dict(self):
|
||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||
|
||||
def serialize(self):
|
||||
blob = self.as_dict()
|
||||
blob['certificates'] = [x.id for x in self.certificate]
|
||||
return blob
|
||||
sensitive = Column(Boolean, default=False)
|
||||
|
|
|
@ -32,6 +32,43 @@ def get_all():
|
|||
return database.find_all(query, Domain, {}).all()
|
||||
|
||||
|
||||
def get_by_name(name):
|
||||
"""
|
||||
Fetches domain by it's name
|
||||
|
||||
:param name:
|
||||
:return:
|
||||
"""
|
||||
return database.get_all(Domain, name, field="name").all()
|
||||
|
||||
|
||||
def create(name, sensitive):
|
||||
"""
|
||||
Create a new domain
|
||||
|
||||
:param name:
|
||||
:param sensitive:
|
||||
:return:
|
||||
"""
|
||||
domain = Domain(name=name, sensitive=sensitive)
|
||||
return database.create(domain)
|
||||
|
||||
|
||||
def update(domain_id, name, sensitive):
|
||||
"""
|
||||
Update an existing domain
|
||||
|
||||
:param domain_id:
|
||||
:param name:
|
||||
:param sensitive:
|
||||
:return:
|
||||
"""
|
||||
domain = get(domain_id)
|
||||
domain.name = name
|
||||
domain.sensitive = sensitive
|
||||
database.update(domain)
|
||||
|
||||
|
||||
def render(args):
|
||||
"""
|
||||
Helper to parse REST Api requests
|
||||
|
|
|
@ -12,12 +12,14 @@ from flask.ext.restful import reqparse, Api, fields
|
|||
|
||||
from lemur.domains import service
|
||||
from lemur.auth.service import AuthenticatedResource
|
||||
from lemur.auth.permissions import SensitiveDomainPermission
|
||||
|
||||
from lemur.common.utils import paginated_parser, marshal_items
|
||||
|
||||
FIELDS = {
|
||||
'id': fields.Integer,
|
||||
'name': fields.String
|
||||
'name': fields.String,
|
||||
'sensitive': fields.Boolean
|
||||
}
|
||||
|
||||
mod = Blueprint('domains', __name__)
|
||||
|
@ -57,10 +59,12 @@ class DomainsList(AuthenticatedResource):
|
|||
{
|
||||
"id": 1,
|
||||
"name": "www.example.com",
|
||||
"sensitive": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "www.example2.com",
|
||||
"sensitive": false
|
||||
}
|
||||
]
|
||||
"total": 2
|
||||
|
@ -79,6 +83,54 @@ class DomainsList(AuthenticatedResource):
|
|||
args = parser.parse_args()
|
||||
return service.render(args)
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def post(self):
|
||||
"""
|
||||
.. http:post:: /domains
|
||||
|
||||
The current domain list
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /domains HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"name": "www.example.com",
|
||||
"sensitive": false
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "www.example.com",
|
||||
"sensitive": false
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
:query filter: key value pair. format is k=v;
|
||||
:query limit: limit number. default is 10
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
:statuscode 403: unauthenticated
|
||||
"""
|
||||
self.reqparse.add_argument('name', type=str, location='json')
|
||||
self.reqparse.add_argument('sensitive', type=bool, default=False, location='json')
|
||||
args = self.reqparse.parse_args()
|
||||
return service.create(args['name'], args['sensitive'])
|
||||
|
||||
|
||||
class Domains(AuthenticatedResource):
|
||||
def __init__(self):
|
||||
|
@ -111,6 +163,7 @@ class Domains(AuthenticatedResource):
|
|||
{
|
||||
"id": 1,
|
||||
"name": "www.example.com",
|
||||
"sensitive": false
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
|
@ -119,6 +172,53 @@ class Domains(AuthenticatedResource):
|
|||
"""
|
||||
return service.get(domain_id)
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def put(self, domain_id):
|
||||
"""
|
||||
.. http:get:: /domains/1
|
||||
|
||||
update one domain
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /domains HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"name": "www.example.com",
|
||||
"sensitive": false
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "www.example.com",
|
||||
"sensitive": false
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
:statuscode 403: unauthenticated
|
||||
"""
|
||||
self.reqparse.add_argument('name', type=str, location='json')
|
||||
self.reqparse.add_argument('sensitive', type=bool, default=False, location='json')
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
if SensitiveDomainPermission().can():
|
||||
return service.update(domain_id, args['name'], args['sensitive'])
|
||||
|
||||
return dict(message='You are not authorized to modify this domain'), 403
|
||||
|
||||
|
||||
class CertificateDomains(AuthenticatedResource):
|
||||
""" Defines the 'domains' endpoint """
|
||||
|
@ -153,10 +253,12 @@ class CertificateDomains(AuthenticatedResource):
|
|||
{
|
||||
"id": 1,
|
||||
"name": "www.example.com",
|
||||
"sensitive": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "www.example2.com",
|
||||
"sensitive": false
|
||||
}
|
||||
]
|
||||
"total": 2
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
"""Adding ability to mark domains as 'sensitive'
|
||||
|
||||
Revision ID: 4c50b903d1ae
|
||||
Revises: 33de094da890
|
||||
Create Date: 2015-12-30 10:19:30.057791
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4c50b903d1ae'
|
||||
down_revision = '33de094da890'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('domains', sa.Column('sensitive', sa.Boolean(), nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('domains', 'sensitive')
|
||||
### end Alembic commands ###
|
|
@ -12,4 +12,12 @@ angular.module('lemur')
|
|||
return domains;
|
||||
});
|
||||
};
|
||||
|
||||
DomainService.updateSensitive = function (domain) {
|
||||
return domain.put();
|
||||
};
|
||||
|
||||
DomainService.create = function (domain) {
|
||||
return DomainApi.post(domain);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ angular.module('lemur')
|
|||
});
|
||||
})
|
||||
|
||||
.controller('DomainsViewController', function ($scope, DomainApi, ngTableParams) {
|
||||
.controller('DomainsViewController', function ($scope, $modal, DomainApi, DomainService, ngTableParams, toaster) {
|
||||
$scope.filter = {};
|
||||
$scope.domainsTable = new ngTableParams({
|
||||
page: 1, // show first page
|
||||
|
@ -29,6 +29,40 @@ angular.module('lemur')
|
|||
}
|
||||
});
|
||||
|
||||
$scope.updateSensitive = function (domain) {
|
||||
DomainService.updateSensitive(domain).then(
|
||||
function () {
|
||||
toaster.pop({
|
||||
type: 'success',
|
||||
title: domain.name,
|
||||
body: 'Updated!'
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
toaster.pop({
|
||||
type: 'error',
|
||||
title: domain.name,
|
||||
body: 'Unable to update! ' + response.data.message,
|
||||
timeout: 100000
|
||||
});
|
||||
domain.sensitive = domain.sensitive ? false : true;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.create = function () {
|
||||
var modalInstance = $modal.open({
|
||||
animation: true,
|
||||
controller: 'DomainsCreateController',
|
||||
templateUrl: '/angular/domains/domain/domain.tpl.html',
|
||||
size: 'lg'
|
||||
});
|
||||
|
||||
modalInstance.result.then(function () {
|
||||
$scope.domainsTable.reload();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.toggleFilter = function (params) {
|
||||
params.settings().$scope.show_filter = !params.settings().$scope.show_filter;
|
||||
};
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
<span class="text-muted"><small>Zone transfers as scary</small></span></h2>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="btn-group pull-right">
|
||||
<button ng-click="create()" class="btn btn-primary">Create</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button ng-click="toggleFilter(domainsTable)" class="btn btn-default">Filter</button>
|
||||
</div>
|
||||
|
@ -16,6 +19,12 @@
|
|||
<td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">
|
||||
{{ domain.name }}
|
||||
</td>
|
||||
<td data-title="'Sensitive'">
|
||||
<form>
|
||||
<switch ng-change="updateSensitive(domain)" id="status" name="status"
|
||||
ng-model="domain.sensitive" class="green small"></switch>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -10,7 +10,7 @@ def test_domain_post(client):
|
|||
|
||||
|
||||
def test_domain_put(client):
|
||||
assert client.put(api.url_for(Domains, domain_id=1), data={}).status_code == 405
|
||||
assert client.put(api.url_for(Domains, domain_id=1), data={}).status_code == 401
|
||||
|
||||
|
||||
def test_domain_delete(client):
|
||||
|
@ -34,7 +34,7 @@ def test_auth_domain_post_(client):
|
|||
|
||||
|
||||
def test_auth_domain_put(client):
|
||||
assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
||||
assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
|
||||
|
||||
|
||||
def test_auth_domain_delete(client):
|
||||
|
@ -58,7 +58,7 @@ def test_admin_domain_post(client):
|
|||
|
||||
|
||||
def test_admin_domain_put(client):
|
||||
assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
||||
assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
|
||||
|
||||
|
||||
def test_admin_domain_delete(client):
|
||||
|
@ -74,7 +74,7 @@ def test_domains_get(client):
|
|||
|
||||
|
||||
def test_domains_post(client):
|
||||
assert client.post(api.url_for(DomainsList), data={}).status_code == 405
|
||||
assert client.post(api.url_for(DomainsList), data={}).status_code == 401
|
||||
|
||||
|
||||
def test_domains_put(client):
|
||||
|
|
Loading…
Reference in New Issue