mirror of
https://github.com/ansible/awx.git
synced 2026-02-21 13:10:11 -03:30
Implements AC-362. Encrypt passwords for credential and project using AES, don't expose passwords via the API.
This commit is contained in:
326
awx/main/migrations/0011_v13_encrypt_passwords.py
Normal file
326
awx/main/migrations/0011_v13_encrypt_passwords.py
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import DataMigration
|
||||||
|
from django.db import models
|
||||||
|
from awx.main.utils import encrypt_field, decrypt_field
|
||||||
|
|
||||||
|
class Migration(DataMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
"Write your forwards methods here."
|
||||||
|
for credential in orm.Credential.objects.all():
|
||||||
|
for field in ('ssh_password', 'ssh_key_data', 'ssh_key_unlock', 'sudo_password'):
|
||||||
|
setattr(credential, field, encrypt_field(credential, field, field != 'ssh_key_data'))
|
||||||
|
credential.save()
|
||||||
|
for project in orm.Project.objects.all():
|
||||||
|
for field in ('scm_password', 'scm_key_data', 'scm_key_unlock'):
|
||||||
|
setattr(project, field, encrypt_field(project, field, field != 'scm_key_data'))
|
||||||
|
project.save()
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
"Write your backwards methods here."
|
||||||
|
for credential in orm.Credential.objects.all():
|
||||||
|
for field in ('ssh_password', 'ssh_key_data', 'ssh_key_unlock', 'sudo_password'):
|
||||||
|
setattr(credential, field, decrypt_field(credential, field))
|
||||||
|
credential.save()
|
||||||
|
for project in orm.Project.objects.all():
|
||||||
|
for field in ('scm_password', 'scm_key_data', 'scm_key_unlock'):
|
||||||
|
setattr(project, field, decrypt_field(project, field))
|
||||||
|
project.save()
|
||||||
|
|
||||||
|
models = {
|
||||||
|
u'auth.group': {
|
||||||
|
'Meta': {'object_name': 'Group'},
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||||
|
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
u'auth.permission': {
|
||||||
|
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||||
|
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||||
|
},
|
||||||
|
u'auth.user': {
|
||||||
|
'Meta': {'object_name': 'User'},
|
||||||
|
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||||
|
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||||
|
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||||
|
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||||
|
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||||
|
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||||
|
},
|
||||||
|
u'contenttypes.contenttype': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||||
|
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||||
|
},
|
||||||
|
'main.credential': {
|
||||||
|
'Meta': {'object_name': 'Credential'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'main.group': {
|
||||||
|
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
|
||||||
|
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.host': {
|
||||||
|
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
|
||||||
|
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.inventory': {
|
||||||
|
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
|
||||||
|
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.job': {
|
||||||
|
'Meta': {'object_name': 'Job'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||||
|
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||||
|
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||||
|
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
|
||||||
|
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
|
||||||
|
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
|
||||||
|
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.jobevent': {
|
||||||
|
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
|
||||||
|
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
u'main.jobhostsummary': {
|
||||||
|
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
|
||||||
|
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||||
|
},
|
||||||
|
'main.jobtemplate': {
|
||||||
|
'Meta': {'object_name': 'JobTemplate'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||||
|
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||||
|
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||||
|
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
|
||||||
|
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.organization': {
|
||||||
|
'Meta': {'object_name': 'Organization'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
|
||||||
|
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
'main.permission': {
|
||||||
|
'Meta': {'object_name': 'Permission'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
|
||||||
|
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
u'main.project': {
|
||||||
|
'Meta': {'object_name': 'Project'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'current_update': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'project_as_current_update+'", 'null': 'True', 'to': "orm['main.ProjectUpdate']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'last_update': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'project_as_last_update+'", 'null': 'True', 'to': "orm['main.ProjectUpdate']"}),
|
||||||
|
'last_update_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'null': 'True', 'blank': 'True'}),
|
||||||
|
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
|
||||||
|
'scm_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||||
|
'scm_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||||
|
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'null': 'True', 'blank': 'True'}),
|
||||||
|
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||||
|
'scm_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'null': 'True', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.projectupdate': {
|
||||||
|
'Meta': {'object_name': 'ProjectUpdate'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'projectupdate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'projectupdate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': u"orm['main.Project']"}),
|
||||||
|
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'})
|
||||||
|
},
|
||||||
|
'main.team': {
|
||||||
|
'Meta': {'object_name': 'Team'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
|
||||||
|
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
|
||||||
|
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
u'taggit.tag': {
|
||||||
|
'Meta': {'object_name': 'Tag'},
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
||||||
|
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
|
||||||
|
},
|
||||||
|
u'taggit.taggeditem': {
|
||||||
|
'Meta': {'object_name': 'TaggedItem'},
|
||||||
|
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
|
||||||
|
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['main']
|
||||||
|
symmetrical = True
|
||||||
@@ -33,6 +33,7 @@ from djcelery.models import TaskMeta
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.compat import slugify
|
from awx.main.compat import slugify
|
||||||
|
from awx.main.utils import encrypt_field, decrypt_field
|
||||||
|
|
||||||
__all__ = ['PrimordialModel', 'Organization', 'Team', 'Project',
|
__all__ = ['PrimordialModel', 'Organization', 'Team', 'Project',
|
||||||
'ProjectUpdate', 'Credential', 'Inventory', 'Host', 'Group',
|
'ProjectUpdate', 'Credential', 'Inventory', 'Host', 'Group',
|
||||||
@@ -409,6 +410,8 @@ class Credential(CommonModelNameNotUnique):
|
|||||||
If used with sudo, a sudo password should be set if required.
|
If used with sudo, a sudo password should be set if required.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
PASSWORD_FIELDS = ('ssh_password', 'ssh_key_data', 'ssh_key_unlock', 'sudo_password')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
|
|
||||||
@@ -462,7 +465,7 @@ class Credential(CommonModelNameNotUnique):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def needs_ssh_key_unlock(self):
|
def needs_ssh_key_unlock(self):
|
||||||
return 'ENCRYPTED' in self.ssh_key_data and \
|
return 'ENCRYPTED' in decrypt_field(self, 'ssh_key_data') and \
|
||||||
(not self.ssh_key_unlock or self.ssh_key_unlock == 'ASK')
|
(not self.ssh_key_unlock or self.ssh_key_unlock == 'ASK')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -480,6 +483,36 @@ class Credential(CommonModelNameNotUnique):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('main:credential_detail', args=(self.pk,))
|
return reverse('main:credential_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
new_instance = not bool(self.pk)
|
||||||
|
# When first saving to the database, don't store any password field
|
||||||
|
# values, but instead save them until after the instance is created.
|
||||||
|
if new_instance:
|
||||||
|
for field in self.PASSWORD_FIELDS:
|
||||||
|
value = getattr(self, field, '')
|
||||||
|
setattr(self, '_saved_%s' % field, value)
|
||||||
|
setattr(self, field, '')
|
||||||
|
# Otherwise, store encrypted values to the database.
|
||||||
|
else:
|
||||||
|
# If update_fields has been specified, add our field names to it,
|
||||||
|
# if hit hasn't been specified, then we're just doing a normal save.
|
||||||
|
update_fields = kwargs.get('update_fields', [])
|
||||||
|
for field in self.PASSWORD_FIELDS:
|
||||||
|
encrypted = encrypt_field(self, field, bool(field != 'ssh_key_data'))
|
||||||
|
setattr(self, field, encrypted)
|
||||||
|
if field not in update_fields:
|
||||||
|
update_fields.append(field)
|
||||||
|
super(Credential, self).save(*args, **kwargs)
|
||||||
|
# After saving a new instance for the first time, set the password
|
||||||
|
# fields and save again.
|
||||||
|
if new_instance:
|
||||||
|
update_fields=[]
|
||||||
|
for field in self.PASSWORD_FIELDS:
|
||||||
|
saved_value = getattr(self, '_saved_%s' % field, '')
|
||||||
|
setattr(self, field, saved_value)
|
||||||
|
update_fields.append(field)
|
||||||
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
class Team(CommonModel):
|
class Team(CommonModel):
|
||||||
'''
|
'''
|
||||||
A team is a group of users that work on common projects.
|
A team is a group of users that work on common projects.
|
||||||
@@ -500,6 +533,8 @@ class Project(CommonModel):
|
|||||||
A project represents a playbook git repo that can access a set of inventories
|
A project represents a playbook git repo that can access a set of inventories
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
PASSWORD_FIELDS = ('scm_password', 'scm_key_data', 'scm_key_unlock')
|
||||||
|
|
||||||
SCM_TYPE_CHOICES = [
|
SCM_TYPE_CHOICES = [
|
||||||
('', _('Manual')),
|
('', _('Manual')),
|
||||||
('git', _('Git')),
|
('git', _('Git')),
|
||||||
@@ -627,12 +662,40 @@ class Project(CommonModel):
|
|||||||
# - masking passwords in project update args/stdout
|
# - masking passwords in project update args/stdout
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
new_instance = not bool(self.pk)
|
||||||
|
# When first saving to the database, don't store any password field
|
||||||
|
# values, but instead save them until after the instance is created.
|
||||||
|
if new_instance:
|
||||||
|
for field in self.PASSWORD_FIELDS:
|
||||||
|
value = getattr(self, field, '')
|
||||||
|
setattr(self, '_saved_%s' % field, value)
|
||||||
|
setattr(self, field, '')
|
||||||
|
# Otherwise, store encrypted values to the database.
|
||||||
|
else:
|
||||||
|
# If update_fields has been specified, add our field names to it,
|
||||||
|
# if hit hasn't been specified, then we're just doing a normal save.
|
||||||
|
update_fields = kwargs.get('update_fields', [])
|
||||||
|
for field in self.PASSWORD_FIELDS:
|
||||||
|
encrypted = encrypt_field(self, field, bool(field != 'scm_key_data'))
|
||||||
|
setattr(self, field, encrypted)
|
||||||
|
if field not in update_fields:
|
||||||
|
update_fields.append(field)
|
||||||
# Check if scm_type or scm_url changes.
|
# Check if scm_type or scm_url changes.
|
||||||
if self.pk:
|
if self.pk:
|
||||||
project_before = Project.objects.get(pk=self.pk)
|
project_before = Project.objects.get(pk=self.pk)
|
||||||
if project_before.scm_type != self.scm_type or project_before.scm_url != self.scm_url:
|
if project_before.scm_type != self.scm_type or project_before.scm_url != self.scm_url:
|
||||||
self.scm_delete_on_next_update = True
|
self.scm_delete_on_next_update = True
|
||||||
super(Project, self).save(*args, **kwargs)
|
super(Project, self).save(*args, **kwargs)
|
||||||
|
# After saving a new instance for the first time, set the password
|
||||||
|
# fields and save again.
|
||||||
|
if new_instance:
|
||||||
|
update_fields=[]
|
||||||
|
for field in self.PASSWORD_FIELDS:
|
||||||
|
saved_value = getattr(self, '_saved_%s' % field, '')
|
||||||
|
setattr(self, field, saved_value)
|
||||||
|
update_fields.append(field)
|
||||||
|
self.save(update_fields=update_fields)
|
||||||
|
# Create auto-generated local path if project uses SCM.
|
||||||
if self.scm_type and not self.local_path.startswith('_'):
|
if self.scm_type and not self.local_path.startswith('_'):
|
||||||
slug_name = slugify(unicode(self.name)).replace(u'-', u'_')
|
slug_name = slugify(unicode(self.name)).replace(u'-', u'_')
|
||||||
self.local_path = u'_%d__%s' % (self.pk, slug_name)
|
self.local_path = u'_%d__%s' % (self.pk, slug_name)
|
||||||
@@ -646,7 +709,7 @@ class Project(CommonModel):
|
|||||||
@property
|
@property
|
||||||
def needs_scm_key_unlock(self):
|
def needs_scm_key_unlock(self):
|
||||||
return self.scm_type and self.scm_key_data and \
|
return self.scm_type and self.scm_key_data and \
|
||||||
'ENCRYPTED' in self.scm_key_data and \
|
'ENCRYPTED' in decrypt_field(self, 'scm_key_data') and \
|
||||||
(not self.scm_key_unlock or self.scm_key_unlock == 'ASK')
|
(not self.scm_key_unlock or self.scm_key_unlock == 'ASK')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ class OrganizationSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class ProjectSerializer(BaseSerializer):
|
class ProjectSerializer(BaseSerializer):
|
||||||
|
|
||||||
|
scm_password = serializers.WritableField(required=False, default='')
|
||||||
|
scm_key_data = serializers.WritableField(required=False, default='')
|
||||||
|
scm_key_unlock = serializers.WritableField(required=False, default='')
|
||||||
|
|
||||||
playbooks = serializers.Field(source='playbooks', help_text='Array of playbooks available within this project.')
|
playbooks = serializers.Field(source='playbooks', help_text='Array of playbooks available within this project.')
|
||||||
scm_delete_on_next_update = serializers.Field(source='scm_delete_on_next_update')
|
scm_delete_on_next_update = serializers.Field(source='scm_delete_on_next_update')
|
||||||
status = serializers.Field(source='status')
|
status = serializers.Field(source='status')
|
||||||
@@ -207,6 +211,22 @@ class ProjectSerializer(BaseSerializer):
|
|||||||
'scm_username', 'scm_password', 'scm_key_data',
|
'scm_username', 'scm_password', 'scm_key_data',
|
||||||
'scm_key_unlock', 'last_update_failed', 'status', 'last_updated')
|
'scm_key_unlock', 'last_update_failed', 'status', 'last_updated')
|
||||||
|
|
||||||
|
def to_native(self, obj):
|
||||||
|
ret = super(ProjectSerializer, self).to_native(obj)
|
||||||
|
# Replace the actual encrypted value with the string $encrypted$.
|
||||||
|
for field in Project.PASSWORD_FIELDS:
|
||||||
|
if field in ret and unicode(ret[field]).startswith('$encrypted$'):
|
||||||
|
ret[field] = '$encrypted$'
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def restore_object(self, attrs, instance=None):
|
||||||
|
# If the value sent to the API startswith $encrypted$, ignore it.
|
||||||
|
for field in Project.PASSWORD_FIELDS:
|
||||||
|
if unicode(attrs.get(field, '')).startswith('$encrypted$'):
|
||||||
|
attrs.pop(field, None)
|
||||||
|
instance = super(ProjectSerializer, self).restore_object(attrs, instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(ProjectSerializer, self).get_related(obj)
|
res = super(ProjectSerializer, self).get_related(obj)
|
||||||
res.update(dict(
|
res.update(dict(
|
||||||
@@ -487,12 +507,33 @@ class CredentialSerializer(BaseSerializer):
|
|||||||
|
|
||||||
# FIXME: may want to make some of these filtered based on user accessing
|
# FIXME: may want to make some of these filtered based on user accessing
|
||||||
|
|
||||||
|
ssh_password = serializers.WritableField(required=False, default='')
|
||||||
|
ssh_key_data = serializers.WritableField(required=False, default='')
|
||||||
|
ssh_key_unlock = serializers.WritableField(required=False, default='')
|
||||||
|
sudo_password = serializers.WritableField(required=False, default='')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Credential
|
model = Credential
|
||||||
fields = BASE_FIELDS + ('ssh_username', 'ssh_password', 'ssh_key_data',
|
fields = BASE_FIELDS + ('ssh_username', 'ssh_password', 'ssh_key_data',
|
||||||
'ssh_key_unlock', 'sudo_username',
|
'ssh_key_unlock', 'sudo_username',
|
||||||
'sudo_password', 'user', 'team',)
|
'sudo_password', 'user', 'team',)
|
||||||
|
|
||||||
|
def to_native(self, obj):
|
||||||
|
ret = super(CredentialSerializer, self).to_native(obj)
|
||||||
|
# Replace the actual encrypted value with the string $encrypted$.
|
||||||
|
for field in Credential.PASSWORD_FIELDS:
|
||||||
|
if field in ret and unicode(ret[field]).startswith('$encrypted$'):
|
||||||
|
ret[field] = '$encrypted$'
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def restore_object(self, attrs, instance=None):
|
||||||
|
# If the value sent to the API startswith $encrypted$, ignore it.
|
||||||
|
for field in Credential.PASSWORD_FIELDS:
|
||||||
|
if unicode(attrs.get(field, '')).startswith('$encrypted$'):
|
||||||
|
attrs.pop(field, None)
|
||||||
|
instance = super(CredentialSerializer, self).restore_object(attrs, instance)
|
||||||
|
return instance
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(CredentialSerializer, self).get_related(obj)
|
res = super(CredentialSerializer, self).get_related(obj)
|
||||||
if obj.user:
|
if obj.user:
|
||||||
@@ -505,9 +546,9 @@ class CredentialSerializer(BaseSerializer):
|
|||||||
''' some fields cannot be changed once written '''
|
''' some fields cannot be changed once written '''
|
||||||
if self.object is not None:
|
if self.object is not None:
|
||||||
# this is an update
|
# this is an update
|
||||||
if self.object.user != attrs['user']:
|
if 'user' in attrs and self.object.user != attrs['user']:
|
||||||
raise serializers.ValidationError("user cannot be changed")
|
raise serializers.ValidationError("user cannot be changed")
|
||||||
if self.object.team != attrs['team']:
|
if 'team' in attrs and self.object.team != attrs['team']:
|
||||||
raise serializers.ValidationError("team cannot be changed")
|
raise serializers.ValidationError("team cannot be changed")
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from django.conf import settings
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import Job, ProjectUpdate
|
from awx.main.models import Job, ProjectUpdate
|
||||||
from awx.main.utils import get_ansible_version
|
from awx.main.utils import get_ansible_version, decrypt_field
|
||||||
|
|
||||||
__all__ = ['RunJob', 'RunProjectUpdate']
|
__all__ = ['RunJob', 'RunProjectUpdate']
|
||||||
|
|
||||||
@@ -63,10 +63,13 @@ class BaseTask(Task):
|
|||||||
'''
|
'''
|
||||||
Create a temporary file containing the SSH private key.
|
Create a temporary file containing the SSH private key.
|
||||||
'''
|
'''
|
||||||
ssh_key_data = getattr(instance, 'ssh_key_data', '')
|
ssh_key_data = ''
|
||||||
if not ssh_key_data:
|
if hasattr(instance, 'scm_key_data'):
|
||||||
credential = getattr(instance, 'credential', None)
|
ssh_key_data = decrypt_field(instance, 'scm_key_data')
|
||||||
ssh_key_data = getattr(credential, 'ssh_key_data', '')
|
elif hasattr(instance, 'credential'):
|
||||||
|
credential = instance.credential
|
||||||
|
if hasattr(credential, 'ssh_key_data'):
|
||||||
|
ssh_key_data = decrypt_field(credential, 'ssh_key_data')
|
||||||
if ssh_key_data:
|
if ssh_key_data:
|
||||||
# FIXME: File permissions?
|
# FIXME: File permissions?
|
||||||
handle, path = tempfile.mkstemp()
|
handle, path = tempfile.mkstemp()
|
||||||
@@ -223,7 +226,7 @@ class RunJob(BaseTask):
|
|||||||
creds = job.credential
|
creds = job.credential
|
||||||
if creds:
|
if creds:
|
||||||
for field in ('ssh_key_unlock', 'ssh_password', 'sudo_password'):
|
for field in ('ssh_key_unlock', 'ssh_password', 'sudo_password'):
|
||||||
value = kwargs.get(field, getattr(creds, field))
|
value = kwargs.get(field, decrypt_field(creds, field))
|
||||||
if value not in ('', 'ASK'):
|
if value not in ('', 'ASK'):
|
||||||
passwords[field] = value
|
passwords[field] = value
|
||||||
return passwords
|
return passwords
|
||||||
@@ -344,11 +347,11 @@ class RunProjectUpdate(BaseTask):
|
|||||||
'''
|
'''
|
||||||
passwords = {}
|
passwords = {}
|
||||||
project = project_update.project
|
project = project_update.project
|
||||||
value = project.scm_key_unlock
|
value = decrypt_field(project, 'scm_key_unlock')
|
||||||
if value not in ('', 'ASK'):
|
if value not in ('', 'ASK'):
|
||||||
passwords['ssh_key_unlock'] = value
|
passwords['ssh_key_unlock'] = value
|
||||||
passwords['scm_username'] = project.scm_username
|
passwords['scm_username'] = project.scm_username
|
||||||
passwords['scm_password'] = project.scm_password
|
passwords['scm_password'] = decrypt_field(project, 'scm_password')
|
||||||
return passwords
|
return passwords
|
||||||
|
|
||||||
def build_env(self, project_update, **kwargs):
|
def build_env(self, project_update, **kwargs):
|
||||||
@@ -383,8 +386,9 @@ class RunProjectUpdate(BaseTask):
|
|||||||
args.append('-%s' % ('v' * 3))
|
args.append('-%s' % ('v' * 3))
|
||||||
project = project_update.project
|
project = project_update.project
|
||||||
scm_url = project.scm_url
|
scm_url = project.scm_url
|
||||||
if project.scm_username and project.scm_password:
|
if project.scm_username and project.scm_password not in ('ASK', ''):
|
||||||
scm_url = self.update_url_auth(scm_url, project.scm_username, project.scm_password)
|
scm_url = self.update_url_auth(scm_url, project.scm_username,
|
||||||
|
decrypt_field(project, 'scm_password'))
|
||||||
elif project.scm_username:
|
elif project.scm_username:
|
||||||
scm_url = self.update_url_auth(scm_url, project.scm_username)
|
scm_url = self.update_url_auth(scm_url, project.scm_username)
|
||||||
# FIXME: Need to hide password in saved job_args and result_stdout!
|
# FIXME: Need to hide password in saved job_args and result_stdout!
|
||||||
@@ -416,7 +420,7 @@ class RunProjectUpdate(BaseTask):
|
|||||||
d.update({
|
d.update({
|
||||||
r'Username for.*:': 'scm_username',
|
r'Username for.*:': 'scm_username',
|
||||||
r'Password for.*:': 'scm_password',
|
r'Password for.*:': 'scm_password',
|
||||||
r'Are you sure you want to continue connecting (yes/no)\?': 'yes',
|
r'Are you sure you want to continue connecting (yes/no)\?': 'yes', # FIXME: Should we really do this?
|
||||||
})
|
})
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -10,6 +12,9 @@ import sys
|
|||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import ParseError, PermissionDenied
|
from rest_framework.exceptions import ParseError, PermissionDenied
|
||||||
|
|
||||||
|
# PyCrypto
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
__all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore',
|
__all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore',
|
||||||
'get_ansible_version', 'get_awx_version']
|
'get_ansible_version', 'get_awx_version']
|
||||||
|
|
||||||
@@ -79,3 +84,45 @@ def get_awx_version():
|
|||||||
return pkg_resources.require('awx')[0].version
|
return pkg_resources.require('awx')[0].version
|
||||||
except:
|
except:
|
||||||
return __version__
|
return __version__
|
||||||
|
|
||||||
|
def get_encryption_key(instance, field_name):
|
||||||
|
'''
|
||||||
|
Generate key for encrypted password based on instance pk and field name.
|
||||||
|
'''
|
||||||
|
from django.conf import settings
|
||||||
|
h = hashlib.sha1()
|
||||||
|
h.update(settings.SECRET_KEY)
|
||||||
|
h.update(str(instance.pk))
|
||||||
|
h.update(field_name)
|
||||||
|
return h.digest()[:16]
|
||||||
|
|
||||||
|
def encrypt_field(instance, field_name, ask=False):
|
||||||
|
'''
|
||||||
|
Return content of the given instance and field name encrypted.
|
||||||
|
'''
|
||||||
|
value = getattr(instance, field_name)
|
||||||
|
if not value or value.startswith('$encrypted$') or (ask and value == 'ASK'):
|
||||||
|
return value
|
||||||
|
key = get_encryption_key(instance, field_name)
|
||||||
|
cipher = AES.new(key, AES.MODE_ECB)
|
||||||
|
while len(value) % cipher.block_size != 0:
|
||||||
|
value += '\x00'
|
||||||
|
encrypted = cipher.encrypt(value)
|
||||||
|
b64data = base64.b64encode(encrypted)
|
||||||
|
return '$encrypted$%s$%s' % ('AES', b64data)
|
||||||
|
|
||||||
|
def decrypt_field(instance, field_name):
|
||||||
|
'''
|
||||||
|
Return content of the given instance and field name decrypted.
|
||||||
|
'''
|
||||||
|
value = getattr(instance, field_name)
|
||||||
|
if not value or not value.startswith('$encrypted$'):
|
||||||
|
return value
|
||||||
|
algo, b64data = value[len('$encrypted$'):].split('$', 1)
|
||||||
|
if algo != 'AES':
|
||||||
|
raise ValueError('unsupported algorithm: %s' % algo)
|
||||||
|
encrypted = base64.b64decode(b64data)
|
||||||
|
key = get_encryption_key(instance, field_name)
|
||||||
|
cipher = AES.new(key, AES.MODE_ECB)
|
||||||
|
value = cipher.decrypt(encrypted)
|
||||||
|
return value.rstrip('\x00')
|
||||||
|
|||||||
Reference in New Issue
Block a user