Remove swauth; update references from swauth to testauth.
This commit is contained in:
@@ -222,22 +222,6 @@ place and then rerun the dispersion report::
|
||||
Sample represents 1.00% of the object partition space
|
||||
|
||||
|
||||
------------------------------------
|
||||
Additional Cleanup Script for Swauth
|
||||
------------------------------------
|
||||
|
||||
With Swauth, you'll want to install a cronjob to clean up any
|
||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
||||
occurs where a single user authenticates several times concurrently. Generally,
|
||||
these orphaned tokens don't pose much of an issue, but it's good to clean them
|
||||
up once a "token life" period (default: 1 day or 86400 seconds).
|
||||
|
||||
This should be as simple as adding `swauth-cleanup-tokens -A
|
||||
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
|
||||
entry on one of the proxies that is running Swauth; but run
|
||||
`swauth-cleanup-tokens` with no arguments for detailed help on the options
|
||||
available.
|
||||
|
||||
------------------------
|
||||
Debugging Tips and Tools
|
||||
------------------------
|
||||
|
||||
@@ -549,35 +549,17 @@ allow_account_management false Whether account PUTs and DELETEs
|
||||
are even callable
|
||||
============================ =============== =============================
|
||||
|
||||
[auth]
|
||||
|
||||
============ =================================== ========================
|
||||
Option Default Description
|
||||
------------ ----------------------------------- ------------------------
|
||||
use Entry point for paste.deploy
|
||||
to use for auth. To
|
||||
use the swift dev auth,
|
||||
set to:
|
||||
`egg:swift#auth`
|
||||
ip 127.0.0.1 IP address of auth
|
||||
server
|
||||
port 11000 Port of auth server
|
||||
ssl False If True, use SSL to
|
||||
connect to auth
|
||||
node_timeout 10 Request timeout
|
||||
============ =================================== ========================
|
||||
|
||||
[swauth]
|
||||
[testauth]
|
||||
|
||||
===================== =============================== =======================
|
||||
Option Default Description
|
||||
--------------------- ------------------------------- -----------------------
|
||||
use Entry point for
|
||||
paste.deploy to use for
|
||||
auth. To use the swauth
|
||||
auth. To use testauth
|
||||
set to:
|
||||
`egg:swift#swauth`
|
||||
set log_name auth-server Label used when logging
|
||||
`egg:swift#testauth`
|
||||
set log_name testauth Label used when logging
|
||||
set log_facility LOG_LOCAL0 Syslog log facility
|
||||
set log_level INFO Log level
|
||||
set log_headers True If True, log headers in
|
||||
@@ -593,16 +575,39 @@ auth_prefix /auth/ The HTTP request path
|
||||
reserves anything
|
||||
beginning with the
|
||||
letter `v`.
|
||||
default_swift_cluster local#http://127.0.0.1:8080/v1 The default Swift
|
||||
cluster to place newly
|
||||
created accounts on.
|
||||
token_life 86400 The number of seconds a
|
||||
token is valid.
|
||||
node_timeout 10 Request timeout
|
||||
super_admin_key None The key for the
|
||||
.super_admin account.
|
||||
===================== =============================== =======================
|
||||
|
||||
Additionally, you need to list all the accounts/users you want here. The format
|
||||
is::
|
||||
|
||||
user_<account>_<user> = <key> [group] [group] [...] [storage_url]
|
||||
|
||||
There are special groups of::
|
||||
|
||||
.reseller_admin = can do anything to any account for this auth
|
||||
.admin = can do anything within the account
|
||||
|
||||
If neither of these groups are specified, the user can only access containers
|
||||
that have been explicitly allowed for them by a .admin or .reseller_admin.
|
||||
|
||||
The trailing optional storage_url allows you to specify an alternate url to
|
||||
hand back to the user upon authentication. If not specified, this defaults to::
|
||||
|
||||
http[s]://<ip>:<port>/v1/<reseller_prefix>_<account>
|
||||
|
||||
Where http or https depends on whether cert_file is specified in the [DEFAULT]
|
||||
section, <ip> and <port> are based on the [DEFAULT] section's bind_ip and
|
||||
bind_port (falling back to 127.0.0.1 and 8080), <reseller_prefix> is from this
|
||||
section, and <account> is from the user_<account>_<user> name.
|
||||
|
||||
Here are example entries, required for running the tests::
|
||||
|
||||
user_admin_admin = admin .admin .reseller_admin
|
||||
user_test_tester = testing .admin
|
||||
user_test2_tester2 = testing2 .admin
|
||||
user_test_tester3 = testing3
|
||||
|
||||
------------------------
|
||||
Memcached Considerations
|
||||
|
||||
@@ -6,7 +6,7 @@ Auth Server and Middleware
|
||||
Creating Your Own Auth Server and Middleware
|
||||
--------------------------------------------
|
||||
|
||||
The included swift/common/middleware/swauth.py is a good example of how to
|
||||
The included swift/common/middleware/testauth.py is a good example of how to
|
||||
create an auth subsystem with proxy server auth middleware. The main points are
|
||||
that the auth middleware can reject requests up front, before they ever get to
|
||||
the Swift Proxy application, and afterwards when the proxy issues callbacks to
|
||||
@@ -27,7 +27,7 @@ specific information, it just passes it along. Convention has
|
||||
environ['REMOTE_USER'] set to the authenticated user string but often more
|
||||
information is needed than just that.
|
||||
|
||||
The included Swauth will set the REMOTE_USER to a comma separated list of
|
||||
The included TestAuth will set the REMOTE_USER to a comma separated list of
|
||||
groups the user belongs to. The first group will be the "user's group", a group
|
||||
that only the user belongs to. The second group will be the "account's group",
|
||||
a group that includes all users for that auth account (different than the
|
||||
@@ -37,7 +37,7 @@ will be omitted.
|
||||
|
||||
It is highly recommended that authentication server implementers prefix their
|
||||
tokens and Swift storage accounts they create with a configurable reseller
|
||||
prefix (`AUTH_` by default with the included Swauth). This prefix will avoid
|
||||
prefix (`AUTH_` by default with the included TestAuth). This prefix will avoid
|
||||
conflicts with other authentication servers that might be using the same
|
||||
Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
|
||||
until one validates a token or all fail.
|
||||
@@ -46,14 +46,14 @@ A restriction with group names is that no group name should begin with a period
|
||||
'.' as that is reserved for internal Swift use (such as the .r for referrer
|
||||
designations as you'll see later).
|
||||
|
||||
Example Authentication with Swauth:
|
||||
Example Authentication with TestAuth:
|
||||
|
||||
* Token AUTH_tkabcd is given to the Swauth middleware in a request's
|
||||
* Token AUTH_tkabcd is given to the TestAuth middleware in a request's
|
||||
X-Auth-Token header.
|
||||
* The Swauth middleware validates the token AUTH_tkabcd and discovers
|
||||
* The TestAuth middleware validates the token AUTH_tkabcd and discovers
|
||||
it matches the "tester" user within the "test" account for the storage
|
||||
account "AUTH_storage_xyz".
|
||||
* The Swauth server sets the REMOTE_USER to
|
||||
* The TestAuth middleware sets the REMOTE_USER to
|
||||
"test:tester,test,AUTH_storage_xyz"
|
||||
* Now this user will have full access (via authorization procedures later)
|
||||
to the AUTH_storage_xyz Swift storage account and access to containers in
|
||||
|
||||
@@ -265,16 +265,18 @@ Sample configuration files are provided with all defaults in line-by-line commen
|
||||
log_facility = LOG_LOCAL1
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = healthcheck cache swauth proxy-server
|
||||
pipeline = healthcheck cache testauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
allow_account_management = true
|
||||
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
# Highly recommended to change this.
|
||||
super_admin_key = swauthkey
|
||||
[filter:testauth]
|
||||
use = egg:swift#testauth
|
||||
user_admin_admin = admin .admin .reseller_admin
|
||||
user_test_tester = testing .admin
|
||||
user_test2_tester2 = testing2 .admin
|
||||
user_test_tester3 = testing3
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
@@ -558,8 +560,10 @@ Setting up scripts for running Swift
|
||||
------------------------------------
|
||||
|
||||
#. Create `~/bin/resetswift.`
|
||||
If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
|
||||
If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
|
||||
|
||||
If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
|
||||
|
||||
If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
@@ -608,18 +612,6 @@ Setting up scripts for running Swift
|
||||
|
||||
swift-init main start
|
||||
|
||||
#. Create `~/bin/recreateaccounts`::
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Replace swauthkey with whatever your super_admin key is (recorded in
|
||||
# /etc/swift/proxy-server.conf).
|
||||
swauth-prep -K swauthkey
|
||||
swauth-add-user -K swauthkey -a test tester testing
|
||||
swauth-add-user -K swauthkey -a test2 tester2 testing2
|
||||
swauth-add-user -K swauthkey test tester3 testing3
|
||||
swauth-add-user -K swauthkey -a -r reseller reseller reseller
|
||||
|
||||
#. Create `~/bin/startrest`::
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
@@ -13,7 +13,7 @@ Prerequisites
|
||||
Basic architecture and terms
|
||||
----------------------------
|
||||
- *node* - a host machine running one or more Swift services
|
||||
- *Proxy node* - node that runs Proxy services; also runs Swauth
|
||||
- *Proxy node* - node that runs Proxy services; also runs TestAuth
|
||||
- *Storage node* - node that runs Account, Container, and Object services
|
||||
- *ring* - a set of mappings of Swift data to physical devices
|
||||
|
||||
@@ -23,7 +23,7 @@ This document shows a cluster using the following types of nodes:
|
||||
|
||||
- Runs the swift-proxy-server processes which proxy requests to the
|
||||
appropriate Storage nodes. The proxy server will also contain
|
||||
the Swauth service as WSGI middleware.
|
||||
the TestAuth service as WSGI middleware.
|
||||
|
||||
- five Storage nodes
|
||||
|
||||
@@ -130,17 +130,15 @@ Configure the Proxy node
|
||||
user = swift
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = healthcheck cache swauth proxy-server
|
||||
pipeline = healthcheck cache testauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
allow_account_management = true
|
||||
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
|
||||
# Highly recommended to change this key to something else!
|
||||
super_admin_key = swauthkey
|
||||
[filter:testauth]
|
||||
use = egg:swift#testauth
|
||||
user_system_root = testpass .admin https://$PROXY_LOCAL_NET_IP:8080/v1/AUTH_system
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
@@ -366,16 +364,6 @@ Create Swift admin account and test
|
||||
|
||||
You run these commands from the Proxy node.
|
||||
|
||||
#. Create a user with administrative privileges (account = system,
|
||||
username = root, password = testpass). Make sure to replace
|
||||
``swauthkey`` with whatever super_admin key you assigned in
|
||||
the proxy-server.conf file
|
||||
above. *Note: None of the values of
|
||||
account, username, or password are special - they can be anything.*::
|
||||
|
||||
swauth-prep -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey
|
||||
swauth-add-user -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey -a system root testpass
|
||||
|
||||
#. Get an X-Storage-Url and X-Auth-Token::
|
||||
|
||||
curl -k -v -H 'X-Storage-User: system:root' -H 'X-Storage-Pass: testpass' https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0
|
||||
@@ -430,45 +418,16 @@ See :ref:`config-proxy` for the initial setup, and then follow these additional
|
||||
use = egg:swift#memcache
|
||||
memcache_servers = $PROXY_LOCAL_NET_IP:11211
|
||||
|
||||
#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
|
||||
#. Change the storage url for any users to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
|
||||
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
|
||||
# Highly recommended to change this key to something else!
|
||||
super_admin_key = swauthkey
|
||||
|
||||
#. The above will make new accounts with the new default_swift_cluster URL, however it won't change any existing accounts. You can change a service URL for existing accounts with::
|
||||
|
||||
First retreve what the URL was::
|
||||
|
||||
swauth-list -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account>
|
||||
|
||||
And then update it with::
|
||||
|
||||
swauth-set-account-service -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account> storage local <new_url_for_the_account>
|
||||
|
||||
Make the <new_url_for_the_account> look just like it's original URL but with the host:port update you want.
|
||||
[filter:testauth]
|
||||
use = egg:swift#testauth
|
||||
user_system_root = testpass .admin http[s]://<LOAD_BALANCER_HOSTNAME>:<PORT>/v1/AUTH_system
|
||||
|
||||
#. Next, copy all the ring information to all the nodes, including your new proxy nodes, and ensure the ring info gets to all the storage nodes as well.
|
||||
|
||||
#. After you sync all the nodes, make sure the admin has the keys in /etc/swift and the ownership for the ring file is correct.
|
||||
|
||||
Additional Cleanup Script for Swauth
|
||||
------------------------------------
|
||||
|
||||
With Swauth, you'll want to install a cronjob to clean up any
|
||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
||||
occurs where a single user authenticates several times concurrently. Generally,
|
||||
these orphaned tokens don't pose much of an issue, but it's good to clean them
|
||||
up once a "token life" period (default: 1 day or 86400 seconds).
|
||||
|
||||
This should be as simple as adding `swauth-cleanup-tokens -A
|
||||
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
|
||||
entry on one of the proxies that is running Swauth; but run
|
||||
`swauth-cleanup-tokens` with no arguments for detailed help on the options
|
||||
available.
|
||||
|
||||
Troubleshooting Notes
|
||||
---------------------
|
||||
If you see problems, look in var/log/syslog (or messages on some distros).
|
||||
|
||||
@@ -33,12 +33,12 @@ Utils
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _common_swauth:
|
||||
.. _common_testauth:
|
||||
|
||||
Swauth
|
||||
======
|
||||
TestAuth
|
||||
========
|
||||
|
||||
.. automodule:: swift.common.middleware.swauth
|
||||
.. automodule:: swift.common.middleware.testauth
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
The Auth System
|
||||
===============
|
||||
|
||||
------
|
||||
Swauth
|
||||
------
|
||||
--------
|
||||
TestAuth
|
||||
--------
|
||||
|
||||
The auth system for Swift is loosely based on the auth system from the existing
|
||||
Rackspace architecture -- actually from a few existing auth systems -- and is
|
||||
@@ -27,7 +27,7 @@ validation.
|
||||
Swift will make calls to the auth system, giving the auth token to be
|
||||
validated. For a valid token, the auth system responds with an overall
|
||||
expiration in seconds from now. Swift will cache the token up to the expiration
|
||||
time. The included Swauth also has the concept of admin and non-admin users
|
||||
time. The included TestAuth also has the concept of admin and non-admin users
|
||||
within an account. Admin users can do anything within the account. Non-admin
|
||||
users can only perform operations per container based on the container's
|
||||
X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
|
||||
@@ -40,152 +40,9 @@ receive the auth token and a URL to the Swift system.
|
||||
Extending Auth
|
||||
--------------
|
||||
|
||||
Swauth is written as wsgi middleware, so implementing your own auth is as easy
|
||||
as writing new wsgi middleware, and plugging it in to the proxy server.
|
||||
TestAuth is written as wsgi middleware, so implementing your own auth is as
|
||||
easy as writing new wsgi middleware, and plugging it in to the proxy server.
|
||||
The KeyStone project and the Swauth project are examples of additional auth
|
||||
services.
|
||||
|
||||
Also, see :doc:`development_auth`.
|
||||
|
||||
|
||||
--------------
|
||||
Swauth Details
|
||||
--------------
|
||||
|
||||
The Swauth system is included at swift/common/middleware/swauth.py; a scalable
|
||||
authentication and authorization system that uses Swift itself as its backing
|
||||
store. This section will describe how it stores its data.
|
||||
|
||||
At the topmost level, the auth system has its own Swift account it stores its
|
||||
own account information within. This Swift account is known as
|
||||
self.auth_account in the code and its name is in the format
|
||||
self.reseller_prefix + ".auth". In this text, we'll refer to this account as
|
||||
<auth_account>.
|
||||
|
||||
The containers whose names do not begin with a period represent the accounts
|
||||
within the auth service. For example, the <auth_account>/test container would
|
||||
represent the "test" account.
|
||||
|
||||
The objects within each container represent the users for that auth service
|
||||
account. For example, the <auth_account>/test/bob object would represent the
|
||||
user "bob" within the auth service account of "test". Each of these user
|
||||
objects contain a JSON dictionary of the format::
|
||||
|
||||
{"auth": "<auth_type>:<auth_value>", "groups": <groups_array>}
|
||||
|
||||
The `<auth_type>` can only be `plaintext` at this time, and the `<auth_value>`
|
||||
is the plain text password itself.
|
||||
|
||||
The `<groups_array>` contains at least two groups. The first is a unique group
|
||||
identifying that user and it's name is of the format `<user>:<account>`. The
|
||||
second group is the `<account>` itself. Additional groups of `.admin` for
|
||||
account administrators and `.reseller_admin` for reseller administrators may
|
||||
exist. Here's an example user JSON dictionary::
|
||||
|
||||
{"auth": "plaintext:testing",
|
||||
"groups": ["name": "test:tester", "name": "test", "name": ".admin"]}
|
||||
|
||||
To map an auth service account to a Swift storage account, the Service Account
|
||||
Id string is stored in the `X-Container-Meta-Account-Id` header for the
|
||||
<auth_account>/<account> container. To map back the other way, an
|
||||
<auth_account>/.account_id/<account_id> object is created with the contents of
|
||||
the corresponding auth service's account name.
|
||||
|
||||
Also, to support a future where the auth service will support multiple Swift
|
||||
clusters or even multiple services for the same auth service account, an
|
||||
<auth_account>/<account>/.services object is created with its contents having a
|
||||
JSON dictionary of the format::
|
||||
|
||||
{"storage": {"default": "local", "local": <url>}}
|
||||
|
||||
The "default" is always "local" right now, and "local" is always the single
|
||||
Swift cluster URL; but in the future there can be more than one cluster with
|
||||
various names instead of just "local", and the "default" key's value will
|
||||
contain the primary cluster to use for that account. Also, there may be more
|
||||
services in addition to the current "storage" service right now.
|
||||
|
||||
Here's an example .services dictionary at the moment::
|
||||
|
||||
{"storage":
|
||||
{"default": "local",
|
||||
"local": "http://127.0.0.1:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
|
||||
|
||||
But, here's an example of what the dictionary may look like in the future::
|
||||
|
||||
{"storage":
|
||||
{"default": "dfw",
|
||||
"dfw": "http://dfw.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"ord": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"sat": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"},
|
||||
"servers":
|
||||
{"default": "dfw",
|
||||
"dfw": "http://dfw.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"ord": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"sat": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
|
||||
|
||||
Lastly, the tokens themselves are stored as objects in the
|
||||
`<auth_account>/.token_[0-f]` containers. The names of the objects are the
|
||||
token strings themselves, such as `AUTH_tked86bbd01864458aa2bd746879438d5a`.
|
||||
The exact `.token_[0-f]` container chosen is based on the final digit of the
|
||||
token name, such as `.token_a` for the token
|
||||
`AUTH_tked86bbd01864458aa2bd746879438d5a`. The contents of the token objects
|
||||
are JSON dictionaries of the format::
|
||||
|
||||
{"account": <account>,
|
||||
"user": <user>,
|
||||
"account_id": <account_id>,
|
||||
"groups": <groups_array>,
|
||||
"expires": <time.time() value>}
|
||||
|
||||
The `<account>` is the auth service account's name for that token. The `<user>`
|
||||
is the user within the account for that token. The `<account_id>` is the
|
||||
same as the `X-Container-Meta-Account-Id` for the auth service's account,
|
||||
as described above. The `<groups_array>` is the user's groups, as described
|
||||
above with the user object. The "expires" value indicates when the token is no
|
||||
longer valid, as compared to Python's time.time() value.
|
||||
|
||||
Here's an example token object's JSON dictionary::
|
||||
|
||||
{"account": "test",
|
||||
"user": "tester",
|
||||
"account_id": "AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"groups": ["name": "test:tester", "name": "test", "name": ".admin"],
|
||||
"expires": 1291273147.1624689}
|
||||
|
||||
To easily map a user to an already issued token, the token name is stored in
|
||||
the user object's `X-Object-Meta-Auth-Token` header.
|
||||
|
||||
Here is an example full listing of an <auth_account>::
|
||||
|
||||
.account_id
|
||||
AUTH_2282f516-559f-4966-b239-b5c88829e927
|
||||
AUTH_f6f57a3c-33b5-4e85-95a5-a801e67505c8
|
||||
AUTH_fea96a36-c177-4ca4-8c7e-b8c715d9d37b
|
||||
.token_0
|
||||
.token_1
|
||||
.token_2
|
||||
.token_3
|
||||
.token_4
|
||||
.token_5
|
||||
.token_6
|
||||
AUTH_tk9d2941b13d524b268367116ef956dee6
|
||||
.token_7
|
||||
.token_8
|
||||
AUTH_tk93627c6324c64f78be746f1e6a4e3f98
|
||||
.token_9
|
||||
.token_a
|
||||
.token_b
|
||||
.token_c
|
||||
.token_d
|
||||
.token_e
|
||||
AUTH_tk0d37d286af2c43ffad06e99112b3ec4e
|
||||
.token_f
|
||||
AUTH_tk766bbde93771489982d8dc76979d11cf
|
||||
reseller
|
||||
.services
|
||||
reseller
|
||||
test
|
||||
.services
|
||||
tester
|
||||
tester3
|
||||
test2
|
||||
.services
|
||||
tester2
|
||||
|
||||
Reference in New Issue
Block a user