Update docs for new LDAP/AD browser-based login flow
Also fix some comments that didn't fit onto one line in the yaml examples, be consistent about putting a blank line above `---` yaml separators, and some other small doc improvements.
This commit is contained in:
parent
aa732a41fb
commit
4101a55001
@ -27,8 +27,8 @@ Create an [ActiveDirectoryIdentityProvider](https://github.com/vmware-tanzu/pinn
|
|||||||
### ActiveDirectoryIdentityProvider with default options
|
### ActiveDirectoryIdentityProvider with default options
|
||||||
|
|
||||||
This ActiveDirectoryIdentityProvider uses all the default configuration options.
|
This ActiveDirectoryIdentityProvider uses all the default configuration options.
|
||||||
|
The default configuration options are documented in the
|
||||||
Learn more about the default configuration [here]({{< ref "../reference/active-directory-configuration">}})
|
[Active Directory configuration reference]({{< ref "../reference/active-directory-configuration">}}).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: idp.supervisor.pinniped.dev/v1alpha1
|
apiVersion: idp.supervisor.pinniped.dev/v1alpha1
|
||||||
@ -41,14 +41,13 @@ spec:
|
|||||||
# Specify the host of the Active Directory server.
|
# Specify the host of the Active Directory server.
|
||||||
host: "activedirectory.example.com:636"
|
host: "activedirectory.example.com:636"
|
||||||
|
|
||||||
# Specify the name of the Kubernetes Secret that contains your Active Directory
|
# Specify the name of the Kubernetes Secret that contains your Active
|
||||||
# bind account credentials. This service account will be used by the
|
# Directory bind account credentials. This service account will be
|
||||||
# Supervisor to perform LDAP user and group searches.
|
# used by the Supervisor to perform LDAP user and group searches.
|
||||||
bind:
|
bind:
|
||||||
secretName: "active-directory-bind-account"
|
secretName: "active-directory-bind-account"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
@ -64,6 +63,10 @@ stringData:
|
|||||||
password: "YOUR_PASSWORD"
|
password: "YOUR_PASSWORD"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the `metadata.name` of the ActiveDirectoryIdentityProvider resource may be visible to end users at login prompts,
|
||||||
|
so choose a name which will be understood by your end users.
|
||||||
|
For example, if you work at Acme Corp, choose something like `acme-corporate-active-directory` over `my-idp`.
|
||||||
|
|
||||||
If you've saved this into a file `activedirectory.yaml`, then install it into your cluster using:
|
If you've saved this into a file `activedirectory.yaml`, then install it into your cluster using:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@ -140,13 +143,16 @@ spec:
|
|||||||
# successful authentication.
|
# successful authentication.
|
||||||
groupName: "dn"
|
groupName: "dn"
|
||||||
|
|
||||||
# Specify the name of the Kubernetes Secret that contains your Active Directory
|
# Specify the name of the Kubernetes Secret that contains your Active
|
||||||
# bind account credentials. This service account will be used by the
|
# Directory bind account credentials. This service account will be
|
||||||
# Supervisor to perform LDAP user and group searches.
|
# used by the Supervisor to perform LDAP user and group searches.
|
||||||
bind:
|
bind:
|
||||||
secretName: "active-directory-bind-account"
|
secretName: "active-directory-bind-account"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
More information about the defaults for these configuration options can be found in
|
||||||
|
the [Active Directory configuration reference]({{< ref "../reference/active-directory-configuration">}}).
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
Next, [configure the Concierge to validate JWTs issued by the Supervisor]({{< ref "configure-concierge-supervisor-jwt" >}})!
|
Next, [configure the Concierge to validate JWTs issued by the Supervisor]({{< ref "configure-concierge-supervisor-jwt" >}})!
|
||||||
|
@ -104,19 +104,21 @@ spec:
|
|||||||
# to the "username" claim in downstream tokens minted by the Supervisor.
|
# to the "username" claim in downstream tokens minted by the Supervisor.
|
||||||
username: email
|
username: email
|
||||||
|
|
||||||
# Specify the name of the claim in your Dex ID token that represents the groups
|
# Specify the name of the claim in your Dex ID token that represents the
|
||||||
# that the user belongs to. This matches what you specified above
|
# groups to which the user belongs. This matches what you specified above
|
||||||
# with the Groups claim filter.
|
# with the Groups claim filter.
|
||||||
# Note that the group claims from Github are in the format of "org:team".
|
# Note that the group claims from Github are in the format of "org:team".
|
||||||
# To query for the group scope, you should set the organization you want Dex to
|
# To query for the group scope, you should set the organization you
|
||||||
# search against in its configuration, otherwise your group claim would be empty.
|
# want Dex to search against in its configuration, otherwise your group
|
||||||
# An example config can be found at - https://dexidp.io/docs/connectors/github/#configuration
|
# claim would be empty. An example config can be found at
|
||||||
|
# https://dexidp.io/docs/connectors/github/#configuration
|
||||||
groups: groups
|
groups: groups
|
||||||
|
|
||||||
# Specify the name of the Kubernetes Secret that contains your Dex
|
# Specify the name of the Kubernetes Secret that contains your Dex
|
||||||
# application's client credentials (created below).
|
# application's client credentials (created below).
|
||||||
client:
|
client:
|
||||||
secretName: dex-client-credentials
|
secretName: dex-client-credentials
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
@ -125,13 +127,19 @@ metadata:
|
|||||||
name: dex-client-credentials
|
name: dex-client-credentials
|
||||||
type: secrets.pinniped.dev/oidc-client
|
type: secrets.pinniped.dev/oidc-client
|
||||||
stringData:
|
stringData:
|
||||||
# The "Client ID" that you set in Dex. For example, in our case this is "pinniped-supervisor"
|
# The "Client ID" that you set in Dex. For example, in our case
|
||||||
|
# this is "pinniped-supervisor".
|
||||||
clientID: "<your-client-id>"
|
clientID: "<your-client-id>"
|
||||||
|
|
||||||
# The "Client secret" that you set in Dex. For example, in our case this is "pinniped-supervisor-secret"
|
# The "Client secret" that you set in Dex. For example, in our
|
||||||
|
# case this is "pinniped-supervisor-secret".
|
||||||
clientSecret: "<your-client-secret>"
|
clientSecret: "<your-client-secret>"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the `metadata.name` of the OIDCIdentityProvider resource may be visible to end users at login prompts
|
||||||
|
if you choose to enable `allowPasswordGrant`, so choose a name which will be understood by your end users.
|
||||||
|
For example, if you work at Acme Corp, choose something like `acme-corporate-ldap` over `my-idp`.
|
||||||
|
|
||||||
Once your OIDCIdentityProvider resource has been created, you can validate your configuration by running:
|
Once your OIDCIdentityProvider resource has been created, you can validate your configuration by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -89,6 +89,7 @@ spec:
|
|||||||
# application's client credentials (created below).
|
# application's client credentials (created below).
|
||||||
client:
|
client:
|
||||||
secretName: gitlab-client-credentials
|
secretName: gitlab-client-credentials
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
@ -105,6 +106,10 @@ stringData:
|
|||||||
clientSecret: "<your-client-secret>"
|
clientSecret: "<your-client-secret>"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the `metadata.name` of the OIDCIdentityProvider resource may be visible to end users at login prompts
|
||||||
|
if you choose to enable `allowPasswordGrant`, so choose a name which will be understood by your end users.
|
||||||
|
For example, if you work at Acme Corp, choose something like `acme-corporate-gitlab` over `my-idp`.
|
||||||
|
|
||||||
Once your OIDCIdentityProvider has been created, you can validate your configuration by running:
|
Once your OIDCIdentityProvider has been created, you can validate your configuration by running:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -120,7 +120,6 @@ spec:
|
|||||||
secretName: "jumpcloudldap-bind-account"
|
secretName: "jumpcloudldap-bind-account"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
@ -138,6 +137,10 @@ stringData:
|
|||||||
password: "YOUR_PASSWORD"
|
password: "YOUR_PASSWORD"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the `metadata.name` of the LDAPIdentityProvider resource may be visible to end users at login prompts,
|
||||||
|
so choose a name which will be understood by your end users.
|
||||||
|
For example, if you work at Acme Corp, choose something like `acme-corporate-ldap` over `my-idp`.
|
||||||
|
|
||||||
If you've saved this into a file `jumpcloud.yaml`, then install it into your cluster using:
|
If you've saved this into a file `jumpcloud.yaml`, then install it into your cluster using:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -97,6 +97,7 @@ spec:
|
|||||||
# application's client credentials (created below).
|
# application's client credentials (created below).
|
||||||
client:
|
client:
|
||||||
secretName: okta-client-credentials
|
secretName: okta-client-credentials
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
@ -113,6 +114,10 @@ stringData:
|
|||||||
clientSecret: "<your-client-secret>"
|
clientSecret: "<your-client-secret>"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the `metadata.name` of the OIDCIdentityProvider resource may be visible to end users at login prompts
|
||||||
|
if you choose to enable `allowPasswordGrant`, so choose a name which will be understood by your end users.
|
||||||
|
For example, if you work at Acme Corp, choose something like `acme-corporate-okta` over `my-idp`.
|
||||||
|
|
||||||
Once your OIDCIdentityProvider has been created, you can validate your configuration by running:
|
Once your OIDCIdentityProvider has been created, you can validate your configuration by running:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -158,6 +158,7 @@ spec:
|
|||||||
- name: certs
|
- name: certs
|
||||||
secret:
|
secret:
|
||||||
secretName: certs
|
secretName: certs
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
@ -265,7 +266,6 @@ spec:
|
|||||||
secretName: openldap-bind-account
|
secretName: openldap-bind-account
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
@ -284,6 +284,10 @@ stringData:
|
|||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the `metadata.name` of the LDAPIdentityProvider resource may be visible to end users at login prompts,
|
||||||
|
so choose a name which will be understood by your end users.
|
||||||
|
For example, if you work at Acme Corp, choose something like `acme-corporate-ldap` over `my-idp`.
|
||||||
|
|
||||||
Once your LDAPIdentityProvider has been created, you can validate your configuration by running:
|
Once your LDAPIdentityProvider has been created, you can validate your configuration by running:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -76,7 +76,8 @@ spec:
|
|||||||
# the default claims in your token. The "openid" scope is always
|
# the default claims in your token. The "openid" scope is always
|
||||||
# included.
|
# included.
|
||||||
#
|
#
|
||||||
# See the example claims below to learn how to customize the claims returned.
|
# See the example claims below to learn how to customize the
|
||||||
|
# claims returned.
|
||||||
additionalScopes: [group, email]
|
additionalScopes: [group, email]
|
||||||
|
|
||||||
# Specify how Workspace ONE Access claims are mapped to Kubernetes identities.
|
# Specify how Workspace ONE Access claims are mapped to Kubernetes identities.
|
||||||
@ -85,22 +86,22 @@ spec:
|
|||||||
# Specify the name of the claim in your Workspace ONE Access token that
|
# Specify the name of the claim in your Workspace ONE Access token that
|
||||||
# will be mapped to the username in your Kubernetes environment.
|
# will be mapped to the username in your Kubernetes environment.
|
||||||
#
|
#
|
||||||
# User's emails can change. Use the sub claim if
|
# User's emails can change. Use the sub claim if your environment
|
||||||
# your environment requires a stable identifier.
|
# requires a stable identifier.
|
||||||
username: email
|
username: email
|
||||||
|
|
||||||
# Specify the name of the claim in Workspace ONE Access that represents the
|
# Specify the name of the claim in Workspace ONE Access that represents
|
||||||
# groups the user belongs to.
|
# the groups to which the user belongs.
|
||||||
#
|
#
|
||||||
# Group names may not be unique and can change.
|
# Group names may not be unique and can change. The group_ids claim is
|
||||||
# The group_ids claim is recommended for environments
|
# recommended for environments that want to use a more stable identifier.
|
||||||
# that want to use a more stable identifier.
|
|
||||||
groups: group_names
|
groups: group_names
|
||||||
|
|
||||||
# Specify the name of the Kubernetes Secret that contains your
|
# Specify the name of the Kubernetes Secret that contains your
|
||||||
# Workspace ONE Access application's client credentials (created below).
|
# Workspace ONE Access application's client credentials (created below).
|
||||||
client:
|
client:
|
||||||
secretName: ws1-client-credentials
|
secretName: ws1-client-credentials
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
@ -117,6 +118,10 @@ stringData:
|
|||||||
clientSecret: "<your-client-secret>"
|
clientSecret: "<your-client-secret>"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the `metadata.name` of the OIDCIdentityProvider resource may be visible to end users at login prompts
|
||||||
|
if you choose to enable `allowPasswordGrant`, so choose a name which will be understood by your end users.
|
||||||
|
For example, if you work at Acme Corp, choose something like `acme-corporate-workspace-one` over `my-idp`.
|
||||||
|
|
||||||
The following claims are returned by Workspace ONE Access. The `group` scope is required to use the
|
The following claims are returned by Workspace ONE Access. The `group` scope is required to use the
|
||||||
`group_ids` and `group_names` claims. The `email` scope is required to use the `email` claim. The
|
`group_ids` and `group_names` claims. The `email` scope is required to use the `email` claim. The
|
||||||
remaining claims are always available.
|
remaining claims are always available.
|
||||||
|
@ -244,6 +244,6 @@ should be signed by a certificate authority that is trusted by their browsers.
|
|||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
Next, configure an OIDCIdentityProvider, ActiveDirectoryIdentityProvider, or an LDAPIdentityProvider for the Supervisor
|
Next, configure an OIDCIdentityProvider, ActiveDirectoryIdentityProvider, or an LDAPIdentityProvider for the Supervisor
|
||||||
(several examples are available in these guides),
|
(several examples are available in these guides). Then
|
||||||
and [configure the Concierge to use the Supervisor for authentication]({{< ref "configure-concierge-supervisor-jwt" >}})
|
[configure the Concierge to use the Supervisor for authentication]({{< ref "configure-concierge-supervisor-jwt" >}})
|
||||||
on each cluster!
|
on each cluster!
|
||||||
|
@ -72,6 +72,9 @@ pinniped get kubeconfig \
|
|||||||
The new Pinniped-compatible kubeconfig YAML will be output as stdout, and can be redirected to a file.
|
The new Pinniped-compatible kubeconfig YAML will be output as stdout, and can be redirected to a file.
|
||||||
|
|
||||||
Various default behaviors of `pinniped get kubeconfig` can be overridden using [its command-line options]({{< ref "cli" >}}).
|
Various default behaviors of `pinniped get kubeconfig` can be overridden using [its command-line options]({{< ref "cli" >}}).
|
||||||
|
One flag of note is `--upstream-identity-provider-flow browser_authcode` to choose end-user `kubectl` login via a web browser
|
||||||
|
(the default for OIDCIdentityProviders), and `--upstream-identity-provider-flow cli_password` to choose end-user `kubectl`
|
||||||
|
login via CLI username/password prompts (the default for LDAPIdentityProviders and ActiveDirectoryIdentityProviders).
|
||||||
|
|
||||||
## Use the generated kubeconfig with `kubectl` to access the cluster
|
## Use the generated kubeconfig with `kubectl` to access the cluster
|
||||||
|
|
||||||
@ -94,20 +97,33 @@ to authenticate the user to the cluster.
|
|||||||
If the Pinniped Supervisor is used for authentication to that cluster, then the user's authentication experience
|
If the Pinniped Supervisor is used for authentication to that cluster, then the user's authentication experience
|
||||||
will depend on which type of identity provider was configured.
|
will depend on which type of identity provider was configured.
|
||||||
|
|
||||||
- For an OIDC identity provider, there are two supported client flows.
|
- For an OIDC identity provider, there are two supported client flows:
|
||||||
|
|
||||||
When using the default browser-based flow, `kubectl` will open the user's web browser and direct it to the login page of
|
1. When using the default browser-based flow, `kubectl` will open the user's web browser and direct it to the login page of
|
||||||
their OIDC Provider. This login flow is controlled by the provider, so it may include two-factor authentication or
|
their OIDC Provider. This login flow is controlled by the provider, so it may include two-factor authentication or
|
||||||
other features provided by the OIDC Provider. If the user's browser is not available, then `kubectl` will instead
|
other features provided by the OIDC Provider. If the user's browser is not available, then `kubectl` will instead
|
||||||
print a URL which can be visited in a browser (potentially on a different computer) to complete the authentication.
|
print a URL which can be visited in a browser (potentially on a different computer) to complete the authentication.
|
||||||
|
|
||||||
When using the optional CLI-based flow, `kubectl` will interactively prompt the user for their username and password at the CLI.
|
2. When using the optional CLI-based flow, `kubectl` will interactively prompt the user for their username and password at the CLI.
|
||||||
|
Alternatively, the user can set the environment variables `PINNIPED_USERNAME` and `PINNIPED_PASSWORD` for the
|
||||||
|
`kubectl` process to avoid the interactive prompts. Note that the optional CLI-based flow must be enabled by the
|
||||||
|
administrator in the OIDCIdentityProvider configuration before use
|
||||||
|
(see `allowPasswordGrant` in the
|
||||||
|
[API docs](https://github.com/vmware-tanzu/pinniped/blob/main/generated/{{< latestcodegenversion >}}/README.adoc#oidcauthorizationconfig)
|
||||||
|
for more details).
|
||||||
|
|
||||||
|
- For LDAP and Active Directory identity providers, there are also two supported client flows:
|
||||||
|
|
||||||
|
1. When using the default CLI-based flow, `kubectl` will interactively prompt the user for their username and password at the CLI.
|
||||||
Alternatively, the user can set the environment variables `PINNIPED_USERNAME` and `PINNIPED_PASSWORD` for the
|
Alternatively, the user can set the environment variables `PINNIPED_USERNAME` and `PINNIPED_PASSWORD` for the
|
||||||
`kubectl` process to avoid the interactive prompts.
|
`kubectl` process to avoid the interactive prompts.
|
||||||
|
|
||||||
- For an LDAP identity provider, `kubectl` will interactively prompt the user for their username and password at the CLI.
|
2. When using the optional browser-based flow, `kubectl` will open the user's web browser and direct it to a login page
|
||||||
Alternatively, the user can set the environment variables `PINNIPED_USERNAME` and `PINNIPED_PASSWORD` for the
|
hosted by the Pinniped Supervisor. When the user enters their username and password, the Supervisor will authenticate
|
||||||
`kubectl` process to avoid the interactive prompts.
|
the user using the LDAP or Active Directory provider. If the user's browser is not available, then `kubectl` will instead
|
||||||
|
print a URL which can be visited in a browser (potentially on a different computer) to complete the authentication.
|
||||||
|
Unlike the optional flow for OIDC providers described above, this optional flow does not need to be configured in
|
||||||
|
the LDAPIdentityProvider or ActiveDirectoryIdentityProvider resource, so it is always available for end-users.
|
||||||
|
|
||||||
Once the user completes authentication, the `kubectl` command will automatically continue and complete the user's requested command.
|
Once the user completes authentication, the `kubectl` command will automatically continue and complete the user's requested command.
|
||||||
For the example above, `kubectl` would list the cluster's namespaces.
|
For the example above, `kubectl` would list the cluster's namespaces.
|
||||||
@ -135,8 +151,14 @@ in the upstream identity provider, for example:
|
|||||||
--group auditors
|
--group auditors
|
||||||
```
|
```
|
||||||
|
|
||||||
## Other notes
|
## Session and credential caching by the CLI
|
||||||
|
|
||||||
- Temporary session credentials such as ID, access, and refresh tokens are stored in:
|
Temporary session credentials such as ID, access, and refresh tokens are stored in:
|
||||||
- `~/.config/pinniped/sessions.yaml` (macOS/Linux)
|
- `$HOME/.config/pinniped/sessions.yaml` (macOS/Linux)
|
||||||
- `%USERPROFILE%/.config/pinniped/sessions.yaml` (Windows).
|
- `%USERPROFILE%/.config/pinniped/sessions.yaml` (Windows).
|
||||||
|
|
||||||
|
Temporary cluster credentials such mTLS client certificates are stored in:
|
||||||
|
- `$HOME/.config/pinniped/credentials.yaml` (macOS/Linux)
|
||||||
|
- `%USERPROFILE%/.config/pinniped/credentials.yaml` (Windows).
|
||||||
|
|
||||||
|
Deleting the contents of these directories is equivalent to performing a client-side logout.
|
||||||
|
@ -206,6 +206,8 @@ The per-FederationDomain endpoints are:
|
|||||||
See [internal/oidc/callback/callback_handler.go](https://github.com/vmware-tanzu/pinniped/blob/main/internal/oidc/callback/callback_handler.go).
|
See [internal/oidc/callback/callback_handler.go](https://github.com/vmware-tanzu/pinniped/blob/main/internal/oidc/callback/callback_handler.go).
|
||||||
- `<issuer_path>/v1alpha1/pinniped_identity_providers` is a custom discovery endpoint for clients to learn about available upstream identity providers.
|
- `<issuer_path>/v1alpha1/pinniped_identity_providers` is a custom discovery endpoint for clients to learn about available upstream identity providers.
|
||||||
See [internal/oidc/idpdiscovery/idp_discovery_handler.go](https://github.com/vmware-tanzu/pinniped/blob/main/internal/oidc/idpdiscovery/idp_discovery_handler.go).
|
See [internal/oidc/idpdiscovery/idp_discovery_handler.go](https://github.com/vmware-tanzu/pinniped/blob/main/internal/oidc/idpdiscovery/idp_discovery_handler.go).
|
||||||
|
- `<issuer_path>/login` is a login UI page to support the optional browser-based login flow for LDAP and Active Directory identity providers.
|
||||||
|
See [internal/oidc/login/login_handler.go](https://github.com/vmware-tanzu/pinniped/blob/main/internal/oidc/login/login_handler.go).
|
||||||
|
|
||||||
The OIDC specifications implemented by the Supervisor can be found at [openid.net](https://openid.net/connect).
|
The OIDC specifications implemented by the Supervisor can be found at [openid.net](https://openid.net/connect).
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user