forms.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. from Crypto.Cipher import PKCS1_OAEP
  2. from Crypto.PublicKey import RSA
  3. from django import forms
  4. from taggit.forms import TagField
  5. from dcim.models import Device
  6. from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldFilterForm, CustomFieldForm
  7. from utilities.forms import (
  8. APISelect, APISelectMultiple, BootstrapMixin, FilterChoiceField, FlexibleModelChoiceField, SlugField,
  9. StaticSelect2Multiple
  10. )
  11. from .models import Secret, SecretRole, UserKey
  12. def validate_rsa_key(key, is_secret=True):
  13. """
  14. Validate the format and type of an RSA key.
  15. """
  16. if key.startswith('ssh-rsa '):
  17. raise forms.ValidationError("OpenSSH line format is not supported. Please ensure that your public is in PEM (base64) format.")
  18. try:
  19. key = RSA.importKey(key)
  20. except ValueError:
  21. raise forms.ValidationError("Invalid RSA key. Please ensure that your key is in PEM (base64) format.")
  22. except Exception as e:
  23. raise forms.ValidationError("Invalid key detected: {}".format(e))
  24. if is_secret and not key.has_private():
  25. raise forms.ValidationError("This looks like a public key. Please provide your private RSA key.")
  26. elif not is_secret and key.has_private():
  27. raise forms.ValidationError("This looks like a private key. Please provide your public RSA key.")
  28. try:
  29. PKCS1_OAEP.new(key)
  30. except Exception:
  31. raise forms.ValidationError("Error validating RSA key. Please ensure that your key supports PKCS#1 OAEP.")
  32. #
  33. # Secret roles
  34. #
  35. class SecretRoleForm(BootstrapMixin, forms.ModelForm):
  36. slug = SlugField()
  37. class Meta:
  38. model = SecretRole
  39. fields = [
  40. 'name', 'slug', 'description', 'users', 'groups',
  41. ]
  42. widgets = {
  43. 'users': StaticSelect2Multiple(),
  44. 'groups': StaticSelect2Multiple(),
  45. }
  46. class SecretRoleCSVForm(CustomFieldForm):
  47. slug = SlugField()
  48. class Meta:
  49. model = SecretRole
  50. fields = SecretRole.csv_headers
  51. help_texts = {
  52. 'name': 'Name of secret role',
  53. }
  54. #
  55. # Secrets
  56. #
  57. class SecretForm(BootstrapMixin, CustomFieldForm):
  58. plaintext = forms.CharField(
  59. max_length=65535,
  60. required=False,
  61. label='Plaintext',
  62. widget=forms.PasswordInput(
  63. attrs={
  64. 'class': 'requires-session-key',
  65. }
  66. )
  67. )
  68. plaintext2 = forms.CharField(
  69. max_length=65535,
  70. required=False,
  71. label='Plaintext (verify)',
  72. widget=forms.PasswordInput()
  73. )
  74. tags = TagField(
  75. required=False
  76. )
  77. class Meta:
  78. model = Secret
  79. fields = [
  80. 'role', 'name', 'plaintext', 'plaintext2', 'tags',
  81. ]
  82. widgets = {
  83. 'role': APISelect(
  84. api_url="/api/secrets/secret-roles/"
  85. )
  86. }
  87. def __init__(self, *args, **kwargs):
  88. super().__init__(*args, **kwargs)
  89. # A plaintext value is required when creating a new Secret
  90. if not self.instance.pk:
  91. self.fields['plaintext'].required = True
  92. def clean(self):
  93. # Verify that the provided plaintext values match
  94. if self.cleaned_data['plaintext'] != self.cleaned_data['plaintext2']:
  95. raise forms.ValidationError({
  96. 'plaintext2': "The two given plaintext values do not match. Please check your input."
  97. })
  98. class SecretCSVForm(CustomFieldForm):
  99. device = FlexibleModelChoiceField(
  100. queryset=Device.objects.all(),
  101. to_field_name='name',
  102. help_text='Device name or ID',
  103. error_messages={
  104. 'invalid_choice': 'Device not found.',
  105. }
  106. )
  107. role = forms.ModelChoiceField(
  108. queryset=SecretRole.objects.all(),
  109. to_field_name='name',
  110. help_text='Name of assigned role',
  111. error_messages={
  112. 'invalid_choice': 'Invalid secret role.',
  113. }
  114. )
  115. plaintext = forms.CharField(
  116. help_text='Plaintext secret data'
  117. )
  118. class Meta:
  119. model = Secret
  120. fields = Secret.csv_headers
  121. help_texts = {
  122. 'name': 'Name or username',
  123. }
  124. def save(self, *args, **kwargs):
  125. s = super().save(*args, **kwargs)
  126. s.plaintext = str(self.cleaned_data['plaintext'])
  127. return s
  128. class SecretBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
  129. pk = forms.ModelMultipleChoiceField(
  130. queryset=Secret.objects.all(),
  131. widget=forms.MultipleHiddenInput()
  132. )
  133. role = forms.ModelChoiceField(
  134. queryset=SecretRole.objects.all(),
  135. required=False,
  136. widget=APISelect(
  137. api_url="/api/secrets/secret-roles/"
  138. )
  139. )
  140. name = forms.CharField(
  141. max_length=100,
  142. required=False
  143. )
  144. class Meta:
  145. nullable_fields = [
  146. 'name',
  147. ]
  148. class SecretFilterForm(BootstrapMixin, CustomFieldFilterForm):
  149. model = Secret
  150. q = forms.CharField(
  151. required=False,
  152. label='Search'
  153. )
  154. role = FilterChoiceField(
  155. queryset=SecretRole.objects.all(),
  156. to_field_name='slug',
  157. widget=APISelectMultiple(
  158. api_url="/api/secrets/secret-roles/",
  159. value_field="slug",
  160. )
  161. )
  162. #
  163. # UserKeys
  164. #
  165. class UserKeyForm(BootstrapMixin, forms.ModelForm):
  166. class Meta:
  167. model = UserKey
  168. fields = ['public_key']
  169. help_texts = {
  170. 'public_key': "Enter your public RSA key. Keep the private one with you; you'll need it for decryption. "
  171. "Please note that passphrase-protected keys are not supported.",
  172. }
  173. labels = {
  174. 'public_key': ''
  175. }
  176. def clean_public_key(self):
  177. key = self.cleaned_data['public_key']
  178. # Validate the RSA key format.
  179. validate_rsa_key(key, is_secret=False)
  180. return key
  181. class ActivateUserKeyForm(forms.Form):
  182. _selected_action = forms.ModelMultipleChoiceField(
  183. queryset=UserKey.objects.all(),
  184. label='User Keys'
  185. )
  186. secret_key = forms.CharField(
  187. widget=forms.Textarea(
  188. attrs={
  189. 'class': 'vLargeTextField',
  190. }
  191. ),
  192. label='Your private key'
  193. )