mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 10:27:37 -02:30
Work in progress on empheral host callback.
This commit is contained in:
@@ -9,6 +9,7 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
|
from awx import MODE
|
||||||
from awx.main.models import *
|
from awx.main.models import *
|
||||||
from awx.main.licenses import LicenseReader
|
from awx.main.licenses import LicenseReader
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from rest_framework import exceptions
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import Job
|
from awx.main.models import Job
|
||||||
|
|
||||||
class JobCallbackAuthentication(authentication.BaseAuthentication):
|
class JobTaskAuthentication(authentication.BaseAuthentication):
|
||||||
'''
|
'''
|
||||||
Custom authentication used for views accessed by the inventory and callback
|
Custom authentication used for views accessed by the inventory and callback
|
||||||
scripts when running a job.
|
scripts when running a job.
|
||||||
@@ -20,9 +20,9 @@ class JobCallbackAuthentication(authentication.BaseAuthentication):
|
|||||||
job = Job.objects.get(pk=job_id, status='running')
|
job = Job.objects.get(pk=job_id, status='running')
|
||||||
except Job.DoesNotExist:
|
except Job.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
token = job.callback_auth_token
|
token = job.task_auth_token
|
||||||
if auth[1] != token:
|
if auth[1] != token:
|
||||||
raise exceptions.AuthenticationFailed('Invalid job callback token')
|
raise exceptions.AuthenticationFailed('Invalid job task token')
|
||||||
return (None, token)
|
return (None, token)
|
||||||
|
|
||||||
def authenticate_header(self, request):
|
def authenticate_header(self, request):
|
||||||
|
|||||||
@@ -645,6 +645,16 @@ class JobTemplate(CommonModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('main:job_template_detail', args=(self.pk,))
|
return reverse('main:job_template_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
def can_start_without_user_input(self):
|
||||||
|
'''Return whether job template can be used to start a new job without
|
||||||
|
requiring any user input.'''
|
||||||
|
if not self.credential:
|
||||||
|
return False
|
||||||
|
for field in ('ssh_password', 'sudo_password', 'ssh_key_unlock'):
|
||||||
|
if getattr(self.credential, 'needs_%s' % field):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
class Job(CommonModel):
|
class Job(CommonModel):
|
||||||
'''
|
'''
|
||||||
A job applies a project (with playbook) to an inventory source with a given
|
A job applies a project (with playbook) to an inventory source with a given
|
||||||
@@ -802,8 +812,8 @@ class Job(CommonModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def callback_auth_token(self):
|
def task_auth_token(self):
|
||||||
'''Return temporary auth token used for task callbacks via API.'''
|
'''Return temporary auth token used for task requests via API.'''
|
||||||
if self.status == 'running':
|
if self.status == 'running':
|
||||||
h = hmac.new(settings.SECRET_KEY, self.created.isoformat())
|
h = hmac.new(settings.SECRET_KEY, self.created.isoformat())
|
||||||
return '%d-%s' % (self.pk, h.hexdigest())
|
return '%d-%s' % (self.pk, h.hexdigest())
|
||||||
|
|||||||
@@ -122,14 +122,29 @@ class CustomRbac(permissions.BasePermission):
|
|||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
return self.has_permission(request, view, obj)
|
return self.has_permission(request, view, obj)
|
||||||
|
|
||||||
class JobCallbackPermission(CustomRbac):
|
class JobTemplateCallbackPermission(CustomRbac):
|
||||||
|
|
||||||
|
def has_permission(self, request, view, obj=None):
|
||||||
|
# If another authentication method was used and it's not a POST, return
|
||||||
|
# True to fall through to the next permission class.
|
||||||
|
if request.user or request.auth and request.method.lower() != 'post':
|
||||||
|
return super(JobTemplateCallbackPermission, self).has_permission(request, view, obj)
|
||||||
|
|
||||||
|
return False
|
||||||
|
# FIXME
|
||||||
|
#try:
|
||||||
|
# job_template = JobTemplate.objects.get(active=True, pk=int(request.auth.split('-')[0]))
|
||||||
|
#except Job.DoesNotExist:
|
||||||
|
# return False
|
||||||
|
|
||||||
|
class JobTaskPermission(CustomRbac):
|
||||||
|
|
||||||
def has_permission(self, request, view, obj=None):
|
def has_permission(self, request, view, obj=None):
|
||||||
|
|
||||||
# If another authentication method was used other than the one for job
|
# If another authentication method was used other than the one for job
|
||||||
# callbacks, return True to fall through to the next permission class.
|
# callbacks, return True to fall through to the next permission class.
|
||||||
if request.user or not request.auth:
|
if request.user or not request.auth:
|
||||||
return super(JobCallbackPermission, self).has_permission(request, view, obj)
|
return super(JobTaskPermission, self).has_permission(request, view, obj)
|
||||||
|
|
||||||
# FIXME: Verify that inventory or job event requested are for the same
|
# FIXME: Verify that inventory or job event requested are for the same
|
||||||
# job ID present in the auth token, etc.
|
# job ID present in the auth token, etc.
|
||||||
|
|||||||
@@ -355,6 +355,8 @@ class JobTemplateSerializer(BaseSerializer):
|
|||||||
))
|
))
|
||||||
if obj.credential:
|
if obj.credential:
|
||||||
res['credential'] = reverse('main:credential_detail', args=(obj.credential.pk,))
|
res['credential'] = reverse('main:credential_detail', args=(obj.credential.pk,))
|
||||||
|
if obj.host_config_key:
|
||||||
|
res['callback'] = reverse('main:job_template_callback', args=(obj.pk,))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def validate_playbook(self, attrs, source):
|
def validate_playbook(self, attrs, source):
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class RunJob(Task):
|
|||||||
if hasattr(settings, 'ANSIBLE_TRANSPORT'):
|
if hasattr(settings, 'ANSIBLE_TRANSPORT'):
|
||||||
env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT')
|
env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT')
|
||||||
env['REST_API_URL'] = settings.INTERNAL_API_URL
|
env['REST_API_URL'] = settings.INTERNAL_API_URL
|
||||||
env['REST_API_TOKEN'] = job.callback_auth_token or ''
|
env['REST_API_TOKEN'] = job.task_auth_token or ''
|
||||||
env['ANSIBLE_NOCOLOR'] = '1' # Prevent output of escape sequences.
|
env['ANSIBLE_NOCOLOR'] = '1' # Prevent output of escape sequences.
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ job_template_urls = patterns('awx.main.views',
|
|||||||
url(r'^$', 'job_template_list'),
|
url(r'^$', 'job_template_list'),
|
||||||
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
|
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
|
||||||
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_jobs_list'),
|
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_jobs_list'),
|
||||||
|
url(r'^(?P<pk>[0-9]+)/callback/$', 'job_template_callback'),
|
||||||
)
|
)
|
||||||
|
|
||||||
job_urls = patterns('awx.main.views',
|
job_urls = patterns('awx.main.views',
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.access import *
|
from awx.main.access import *
|
||||||
from awx.main.authentication import JobCallbackAuthentication
|
from awx.main.authentication import JobTaskAuthentication
|
||||||
from awx.main.licenses import LicenseReader
|
from awx.main.licenses import LicenseReader
|
||||||
from awx.main.base_views import *
|
from awx.main.base_views import *
|
||||||
from awx.main.models import *
|
from awx.main.models import *
|
||||||
@@ -1014,9 +1014,9 @@ class InventoryScriptView(generics.RetrieveAPIView):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
model = Inventory
|
model = Inventory
|
||||||
authentication_classes = [JobCallbackAuthentication] + \
|
authentication_classes = [JobTaskAuthentication] + \
|
||||||
api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
||||||
permission_classes = (JobCallbackPermission,)
|
permission_classes = (JobTaskPermission,)
|
||||||
filter_backends = ()
|
filter_backends = ()
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
@@ -1066,6 +1066,35 @@ class JobTemplateDetail(BaseDetail):
|
|||||||
serializer_class = JobTemplateSerializer
|
serializer_class = JobTemplateSerializer
|
||||||
permission_classes = (CustomRbac,)
|
permission_classes = (CustomRbac,)
|
||||||
|
|
||||||
|
class JobTemplateCallback(generics.RetrieveAPIView):
|
||||||
|
'''
|
||||||
|
Configure a host to POST to this resource using the `host_config_key`.
|
||||||
|
'''
|
||||||
|
|
||||||
|
model = JobTemplate
|
||||||
|
permission_classes = (JobTemplateCallbackPermission,)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
obj = self.get_object()
|
||||||
|
data = dict(
|
||||||
|
host_config_key=obj.host_config_key,
|
||||||
|
)
|
||||||
|
return Response(data)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
obj = self.get_object()
|
||||||
|
# Permission class should have already validated host_config_key.
|
||||||
|
# FIXME: Find host from request.
|
||||||
|
limit = obj.limit
|
||||||
|
# FIXME: Update limit based on host.
|
||||||
|
job = obj.create_job(limit=limit)
|
||||||
|
result = job.start()
|
||||||
|
if not result:
|
||||||
|
data = dict(passwords_needed_to_start=job.get_passwords_needed_to_start())
|
||||||
|
return Response(data, status=400)
|
||||||
|
else:
|
||||||
|
return Response(status=202)
|
||||||
|
|
||||||
class JobTemplateJobsList(BaseSubList):
|
class JobTemplateJobsList(BaseSubList):
|
||||||
|
|
||||||
model = Job
|
model = Job
|
||||||
@@ -1258,9 +1287,9 @@ class GroupJobEventsList(BaseJobEventsList):
|
|||||||
class JobJobEventsList(BaseJobEventsList):
|
class JobJobEventsList(BaseJobEventsList):
|
||||||
|
|
||||||
parent_model = Job
|
parent_model = Job
|
||||||
authentication_classes = [JobCallbackAuthentication] + \
|
authentication_classes = [JobTaskAuthentication] + \
|
||||||
api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
||||||
permission_classes = (JobCallbackPermission,)
|
permission_classes = (JobTaskPermission,)
|
||||||
|
|
||||||
# Post allowed for job event callback only.
|
# Post allowed for job event callback only.
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
|||||||
Reference in New Issue
Block a user