Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gutyán Gábor
/
circlestack
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
1ee90a64
authored
Jun 25, 2014
by
Bach Dániel
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature-ssh-key' into 'master'
Feature ssh key management
parents
2a386b8e
0b3d2280
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
329 additions
and
2 deletions
+329
-2
circle/circle/settings/base.py
+1
-0
circle/dashboard/forms.py
+28
-0
circle/dashboard/models.py
+34
-0
circle/dashboard/tables.py
+31
-0
circle/dashboard/templates/dashboard/profile_form.html
+17
-0
circle/dashboard/templates/dashboard/userkey-create.html
+23
-0
circle/dashboard/templates/dashboard/userkey-edit.html
+24
-0
circle/dashboard/templates/dashboard/userkey-list/column-userkey-actions.html
+7
-0
circle/dashboard/tests/test_views.py
+59
-0
circle/dashboard/urls.py
+11
-0
circle/dashboard/views.py
+78
-2
circle/vm/tasks/agent_tasks.py
+15
-0
requirements/base.txt
+1
-0
No files found.
circle/circle/settings/base.py
View file @
1ee90a64
...
...
@@ -258,6 +258,7 @@ THIRD_PARTY_APPS = (
'sizefield'
,
'taggit'
,
'statici18n'
,
'django_sshkey'
,
)
# Apps specific for this project go here.
...
...
circle/dashboard/forms.py
View file @
1ee90a64
...
...
@@ -40,6 +40,7 @@ from django.template.loader import render_to_string
from
django.utils.translation
import
ugettext
as
_
from
sizefield.widgets
import
FileSizeWidget
from
django_sshkey.models
import
UserKey
from
firewall.models
import
Vlan
,
Host
from
storage.models
import
Disk
from
vm.models
import
(
...
...
@@ -1119,3 +1120,30 @@ class UserCreationForm(OrgUserCreationForm):
if
commit
:
user
.
save
()
return
user
class
UserKeyForm
(
forms
.
ModelForm
):
name
=
forms
.
CharField
(
required
=
True
,
label
=
_
(
'Name'
))
key
=
forms
.
CharField
(
label
=
_
(
'Key'
),
required
=
True
,
help_text
=
_
(
'For example: ssh-rsa AAAAB3NzaC1yc2ED...'
),
widget
=
forms
.
Textarea
(
attrs
=
{
'rows'
:
5
}))
class
Meta
:
fields
=
(
'name'
,
'key'
)
model
=
UserKey
@property
def
helper
(
self
):
helper
=
FormHelper
()
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"Save"
)))
return
helper
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
user
=
kwargs
.
pop
(
"user"
,
None
)
super
(
UserKeyForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
clean
(
self
):
if
self
.
user
:
self
.
instance
.
user
=
self
.
user
return
super
(
UserKeyForm
,
self
)
.
clean
()
circle/dashboard/models.py
View file @
1ee90a64
...
...
@@ -29,9 +29,11 @@ from django.db.models import (
Model
,
ForeignKey
,
OneToOneField
,
CharField
,
IntegerField
,
TextField
,
DateTimeField
,
permalink
,
BooleanField
)
from
django.db.models.signals
import
post_save
,
pre_delete
from
django.template.loader
import
render_to_string
from
django.templatetags.static
import
static
from
django.utils.translation
import
ugettext_lazy
as
_
,
override
,
ugettext
from
django_sshkey.models
import
UserKey
from
model_utils.models
import
TimeStampedModel
from
model_utils.fields
import
StatusField
...
...
@@ -39,6 +41,8 @@ from model_utils import Choices
from
acl.models
import
AclBase
from
vm.tasks.agent_tasks
import
add_keys
,
del_keys
logger
=
getLogger
(
__name__
)
...
...
@@ -224,3 +228,33 @@ if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
else
:
logger
.
debug
(
"Do not register save_org_id to djangosaml2 pre_user_save"
)
def
add_ssh_keys
(
sender
,
**
kwargs
):
from
vm.models
import
Instance
userkey
=
kwargs
.
get
(
'instance'
)
instances
=
Instance
.
get_objects_with_level
(
'user'
,
userkey
.
user
)
.
filter
(
status
=
'RUNNING'
)
for
i
in
instances
:
logger
.
info
(
'called add_keys(
%
s,
%
s)'
,
i
,
userkey
)
queue
=
i
.
get_remote_queue_name
(
"agent"
)
add_keys
.
apply_async
(
args
=
(
i
.
vm_name
,
[
userkey
.
key
]),
queue
=
queue
)
def
del_ssh_keys
(
sender
,
**
kwargs
):
from
vm.models
import
Instance
userkey
=
kwargs
.
get
(
'instance'
)
instances
=
Instance
.
get_objects_with_level
(
'user'
,
userkey
.
user
)
.
filter
(
status
=
'RUNNING'
)
for
i
in
instances
:
logger
.
info
(
'called del_keys(
%
s,
%
s)'
,
i
,
userkey
)
queue
=
i
.
get_remote_queue_name
(
"agent"
)
del_keys
.
apply_async
(
args
=
(
i
.
vm_name
,
[
userkey
.
key
]),
queue
=
queue
)
post_save
.
connect
(
add_ssh_keys
,
sender
=
UserKey
)
pre_delete
.
connect
(
del_ssh_keys
,
sender
=
UserKey
)
circle/dashboard/tables.py
View file @
1ee90a64
...
...
@@ -24,6 +24,7 @@ from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
from
vm.models
import
Instance
,
Node
,
InstanceTemplate
,
Lease
from
django.utils.translation
import
ugettext_lazy
as
_
from
django_sshkey.models
import
UserKey
class
VmListTable
(
Table
):
...
...
@@ -291,3 +292,33 @@ class LeaseListTable(Table):
fields
=
(
'name'
,
'suspend_interval_seconds'
,
'delete_interval_seconds'
,
)
prefix
=
"lease-"
class
UserKeyListTable
(
Table
):
name
=
LinkColumn
(
'dashboard.views.userkey-detail'
,
args
=
[
A
(
'pk'
)],
verbose_name
=
_
(
"Name"
),
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
fingerprint
=
Column
(
verbose_name
=
_
(
"Fingerprint"
),
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
created
=
Column
(
verbose_name
=
_
(
"Created at"
),
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
actions
=
TemplateColumn
(
verbose_name
=
_
(
"Actions"
),
template_name
=
"dashboard/userkey-list/column-userkey-actions.html"
,
orderable
=
False
,
)
class
Meta
:
model
=
UserKey
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
)}
fields
=
(
'name'
,
'fingerprint'
,
'created'
,
'actions'
)
circle/dashboard/templates/dashboard/profile_form.html
View file @
1ee90a64
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load render_table from django_tables2 %}
{% block title-page %}{% trans "Profile" %}{% endblock %}
...
...
@@ -49,4 +50,20 @@
</div>
</div>
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
href=
"{% url "
dashboard
.
views
.
userkey-create
"
%}"
class=
"pull-right btn btn-success btn-xs"
style=
"margin-right: 10px;"
>
<i
class=
"icon-plus"
></i>
{% trans "add SSH key" %}
</a>
<h3
class=
"no-margin"
><i
class=
"icon-key"
></i>
{% trans "SSH public keys" %}
</h3>
</div>
<div
class=
"panel-body"
>
{% render_table userkey_table %}
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/userkey-create.html
0 → 100644
View file @
1ee90a64
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create SSH public key" %}{% endblock %}
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
class=
"pull-right btn btn-default btn-xs"
href=
"{% url "
dashboard
.
views
.
profile-preferences
"
%}"
>
{% trans "Back" %}
</a>
<h3
class=
"no-margin"
><i
class=
"icon-key"
></i>
{% trans "Create SSH public key" %}
</h3>
</div>
<div
class=
"panel-body"
>
{% crispy form %}
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/userkey-edit.html
0 → 100644
View file @
1ee90a64
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Edit SSH public key" %}{% endblock %}
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
class=
"pull-right btn btn-default btn-xs"
href=
"{% url "
dashboard
.
views
.
profile-preferences
"
%}"
>
{% trans "Back" %}
</a>
<h3
class=
"no-margin"
><i
class=
"icon-key"
></i>
{% trans "Edit SSH public key" %}
</h3>
</div>
<div
class=
"panel-body"
>
{% crispy form %}
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/userkey-list/column-userkey-actions.html
0 → 100644
View file @
1ee90a64
{% load i18n %}
<a
href=
"{% url "
dashboard
.
views
.
userkey-detail
"
pk=
record.pk%}"
id=
"template-list-edit-button"
class=
"btn btn-default btn-xs"
title=
"{% trans "
Edit
"
%}"
>
<i
class=
"icon-edit"
></i>
</a>
<a
data-template-pk=
"{{ record.pk }}"
href=
"{% url "
dashboard
.
views
.
userkey-delete
"
pk=
record.pk
%}"
class=
"btn btn-danger btn-xs template-delete"
title=
"{% trans "
Delete
"
%}"
>
<i
class=
"icon-remove"
></i>
</a>
circle/dashboard/tests/test_views.py
View file @
1ee90a64
...
...
@@ -32,6 +32,7 @@ from ..views import VmRenewView
from
storage.models
import
Disk
from
firewall.models
import
Vlan
,
Host
,
VlanGroup
from
mock
import
Mock
,
patch
from
django_sshkey.models
import
UserKey
import
django.conf
settings
=
django
.
conf
.
settings
.
FIREWALL_SETTINGS
...
...
@@ -1959,3 +1960,61 @@ class VmListTest(LoginMixin, TestCase):
's'
:
"A:B:C:D:"
})
self
.
assertEqual
(
200
,
resp
.
status_code
)
class
SshKeyTest
(
LoginMixin
,
TestCase
):
def
setUp
(
self
):
self
.
u1
=
User
.
objects
.
create
(
username
=
'user1'
)
self
.
u1
.
set_password
(
'password'
)
self
.
u1
.
save
()
self
.
u2
=
User
.
objects
.
create
(
username
=
'user2'
)
self
.
u2
.
set_password
(
'password'
)
self
.
u2
.
save
()
self
.
k1
=
UserKey
(
key
=
'ssh-rsa AAAAB3NzaC1yc2EC asd'
,
user
=
self
.
u1
)
self
.
k1
.
save
()
def
tearDown
(
self
):
super
(
SshKeyTest
,
self
)
.
tearDown
()
self
.
k1
.
delete
()
self
.
u1
.
delete
()
def
test_permitted_edit
(
self
):
c
=
Client
()
self
.
login
(
c
,
self
.
u1
)
resp
=
c
.
post
(
"/dashboard/sshkey/1/"
,
{
'key'
:
'ssh-rsa AAAAB3NzaC1yc2EC'
})
self
.
assertEqual
(
UserKey
.
objects
.
get
(
id
=
1
)
.
user
,
self
.
u1
)
self
.
assertEqual
(
200
,
resp
.
status_code
)
def
test_unpermitted_edit
(
self
):
c
=
Client
()
self
.
login
(
c
,
self
.
u2
)
resp
=
c
.
post
(
"/dashboard/sshkey/1/"
,
{
'key'
:
'ssh-rsa AAAAB3NzaC1yc2EC'
})
self
.
assertEqual
(
UserKey
.
objects
.
get
(
id
=
1
)
.
user
,
self
.
u1
)
self
.
assertEqual
(
403
,
resp
.
status_code
)
def
test_permitted_add
(
self
):
c
=
Client
()
self
.
login
(
c
,
self
.
u1
)
resp
=
c
.
post
(
"/dashboard/sshkey/create/"
,
{
'name'
:
'asd'
,
'key'
:
'ssh-rsa AAAAB3NzaC1yc2EC'
})
self
.
assertEqual
(
UserKey
.
objects
.
get
(
id
=
2
)
.
user
,
self
.
u1
)
self
.
assertEqual
(
302
,
resp
.
status_code
)
def
test_permitted_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
self
.
u1
)
resp
=
c
.
post
(
"/dashboard/sshkey/delete/1/"
)
self
.
assertEqual
(
302
,
resp
.
status_code
)
def
test_unpermitted_delete
(
self
):
c
=
Client
()
self
.
login
(
c
,
self
.
u2
)
resp
=
c
.
post
(
"/dashboard/sshkey/delete/1/"
)
self
.
assertEqual
(
403
,
resp
.
status_code
)
circle/dashboard/urls.py
View file @
1ee90a64
...
...
@@ -36,6 +36,7 @@ from .views import (
UserCreationView
,
get_vm_screenshot
,
ProfileView
,
toggle_use_gravatar
,
UnsubscribeFormView
,
UserKeyDelete
,
UserKeyDetail
,
UserKeyCreate
,
)
urlpatterns
=
patterns
(
...
...
@@ -158,4 +159,14 @@ urlpatterns = patterns(
url
(
r'^group/(?P<group_pk>\d+)/create/$'
,
UserCreationView
.
as_view
(),
name
=
"dashboard.views.create-user"
),
url
(
r'^sshkey/delete/(?P<pk>\d+)/$'
,
UserKeyDelete
.
as_view
(),
name
=
"dashboard.views.userkey-delete"
),
url
(
r'^sshkey/(?P<pk>\d+)/$'
,
UserKeyDetail
.
as_view
(),
name
=
"dashboard.views.userkey-detail"
),
url
(
r'^sshkey/create/$'
,
UserKeyCreate
.
as_view
(),
name
=
"dashboard.views.userkey-create"
),
)
circle/dashboard/views.py
View file @
1ee90a64
...
...
@@ -53,17 +53,19 @@ from braces.views import (LoginRequiredMixin, SuperuserRequiredMixin,
PermissionRequiredMixin
)
from
braces.views._access
import
AccessMixin
from
django_sshkey.models
import
UserKey
from
.forms
import
(
CircleAuthenticationForm
,
HostForm
,
LeaseForm
,
MyProfileForm
,
NodeForm
,
TemplateForm
,
TraitForm
,
VmCustomizeForm
,
GroupCreateForm
,
UserCreationForm
,
GroupProfileUpdateForm
,
UnsubscribeForm
,
VmSaveForm
,
VmSaveForm
,
UserKeyForm
,
CirclePasswordChangeForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
)
from
.tables
import
(
NodeListTable
,
NodeVmListTable
,
TemplateListTable
,
LeaseListTable
,
GroupListTable
,
GroupListTable
,
UserKeyListTable
)
from
vm.models
import
(
Instance
,
instance_activity
,
InstanceActivity
,
InstanceTemplate
,
Interface
,
...
...
@@ -2538,6 +2540,11 @@ class MyPreferencesView(UpdateView):
user
=
self
.
request
.
user
),
'change_language'
:
MyProfileForm
(
instance
=
self
.
get_object
()),
}
table
=
UserKeyListTable
(
UserKey
.
objects
.
filter
(
user
=
self
.
request
.
user
),
request
=
self
.
request
)
table
.
page
=
None
context
[
'userkey_table'
]
=
table
return
context
def
get_object
(
self
,
queryset
=
None
):
...
...
@@ -2855,3 +2862,72 @@ def toggle_use_gravatar(request, **kwargs):
json
.
dumps
({
'new_avatar_url'
:
new_avatar_url
}),
content_type
=
"application/json"
,
)
class
UserKeyDetail
(
LoginRequiredMixin
,
SuccessMessageMixin
,
UpdateView
):
model
=
UserKey
template_name
=
"dashboard/userkey-edit.html"
form_class
=
UserKeyForm
success_message
=
_
(
"Successfully modified SSH key."
)
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
return
super
(
UserKeyDetail
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.userkey-detail"
,
kwargs
=
self
.
kwargs
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
return
super
(
UserKeyDetail
,
self
)
.
post
(
self
,
request
,
args
,
kwargs
)
class
UserKeyDelete
(
LoginRequiredMixin
,
DeleteView
):
model
=
UserKey
def
get_success_url
(
self
):
return
reverse
(
"dashboard.views.profile-preferences"
)
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
object
.
delete
()
success_url
=
self
.
get_success_url
()
success_message
=
_
(
"SSH key successfully deleted."
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'message'
:
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
success_url
)
class
UserKeyCreate
(
LoginRequiredMixin
,
SuccessMessageMixin
,
CreateView
):
model
=
UserKey
form_class
=
UserKeyForm
template_name
=
"dashboard/userkey-create.html"
success_message
=
_
(
"Successfully created a new SSH key."
)
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.profile-preferences"
)
def
get_form_kwargs
(
self
):
kwargs
=
super
(
UserKeyCreate
,
self
)
.
get_form_kwargs
()
kwargs
[
'user'
]
=
self
.
request
.
user
return
kwargs
circle/vm/tasks/agent_tasks.py
View file @
1ee90a64
...
...
@@ -56,3 +56,18 @@ def start_access_server(vm):
@celery.task
(
name
=
'agent.update'
)
def
update
(
vm
,
data
):
pass
@celery.task
(
name
=
'agent.add_keys'
)
def
add_keys
(
vm
,
keys
):
pass
@celery.task
(
name
=
'agent.del_keys'
)
def
del_keys
(
vm
,
keys
):
pass
@celery.task
(
name
=
'agent.get_keys'
)
def
get_keys
(
vm
):
pass
requirements/base.txt
View file @
1ee90a64
...
...
@@ -9,6 +9,7 @@ django-celery==3.1.10
django-crispy-forms==1.4.0
django-model-utils==2.0.3
django-sizefield==0.4
django-sshkey==2.2.0
django-statici18n==1.1
django-tables2==0.15.0
django-taggit==0.12
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment