Skip to content

Commit

Permalink
Merge pull request #20 from cyberark/auth-strategry-refactor
Browse files Browse the repository at this point in the history
Abstract authentication flow into new interface
  • Loading branch information
szh authored Jun 1, 2022
2 parents 6a5f738 + deb6615 commit 24a1133
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 95 deletions.
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added
- The `get_server_info` method is now available in SDK. It is only supported against Conjur enterprise server
### Changed
### Deprecated
### Removed
### Fixed
### Security
- Abstract authentication flow into new `AuthenticationStrategyInterface`
[conjur-api-python#20](https://github.com/cyberark/conjur-api-python/pull/20)

## [8.0.0] - 2022-05-25

[Unreleased]: https://github.com/cyberark/conjur-api-python/compare/v8.0.0...HEAD
[8.0.0]: https://github.com/cyberark/conjur-api-python/releases/tag/v8.0.0
46 changes: 30 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ python API!
The SDK can be installed via PyPI. Note that the SDK is a **Community level** project meaning that the SDK is subject to
alterations that may result in breaking change.

```
```sh

pip3 install conjur

```

To avoid unanticipated breaking changes, make sure that you stay up-to-date on our latest releases and review the
Expand All @@ -48,8 +50,10 @@ source and not necessarily an official release.

If you wish to install the library from the source clone the [project](https://github.com/cyberark/conjur-api-python) and run:

```
```sh

pip3 install .

```

### Configuring the client
Expand All @@ -58,7 +62,7 @@ pip3 install .

In order to login to conjur you need to have 5 parameters known from advance.

```
```python
conjur_url = "https://my_conjur.com"
account = "my_account"
username = "user1"
Expand All @@ -70,7 +74,9 @@ ssl_verification_mode = SslVerificationMode.TRUST_STORE

ConjurConnectionInfo is a data class containing all the non-credentials connection details.

`connection_info = ConjurConnectionInfo(conjur_url=conjur_url,account=account,cert_file = None)`
```python
connection_info = ConjurConnectionInfo(conjur_url=conjur_url,account=account,cert_file=None)
```

* conjur_url - url of conjur server
* account - the account which we want to connect to
Expand All @@ -88,34 +94,42 @@ fit (`keyring` usage for example)
We also provide the user with a simple implementation of such provider called `SimpleCredentialsProvider`. Example of
creating such provider + storing credentials:

```
```python
credentials = CredentialsData(username=username, password=password, machine=conjur_url)
credentials_provider = SimpleCredentialsProvider()
credentials_provider.save(credentials)
del credentials
```

#### Creating the client and use it
#### Create authentication strategy

Now that we have created `connection_info` and `credentials_provider`
We can create our client
The client also uses an authentication strategy in order to authenticate to conjur. This approach allows us to implement different authentication strategies
(e.g. `authn`, `authn-ldap`, `authn-k8s`) and to keep the authentication logic separate from the client implementation.

We provide the `AuthnAuthenticationStrategy` for the default Conjur authenticator. Example use:

```python
authn_provider = AuthnAuthenticationStrategy(credentials_provider)
```
client = Client(connection_info, credentials_provider=credentials_provider, ssl_verification_mode=ssl_verification_mode)

#### Creating the client and use it

Now that we have created `connection_info` and `authn_provider`, we can create our client:

```python
client = Client(connection_info,
authn_strategy=authn_provider,
ssl_verification_mode=ssl_verification_mode)
```

* ssl_verification_mode = `SslVerificationMode` enum that states what is the certificate verification technique we will
use when making the api request

After creating the client we can login to conjur and start using it. Example of usage:

```
client.login() # login to conjur and return the api_key`
client.list() # get list of all conjur resources that the user authorize to read`
```python
client.login() # login to conjur and return the api_key
client.list() # get list of all conjur resources that the user authorize to read
```

## Supported Client methods
Expand Down
1 change: 1 addition & 0 deletions conjur_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from conjur_api.client import Client
from conjur_api.interface import CredentialsProviderInterface
from conjur_api.interface import AuthenticationStrategyInterface
from conjur_api import models
from conjur_api import errors
from conjur_api import providers
16 changes: 6 additions & 10 deletions conjur_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
import json
import logging
from typing import Optional
from conjur_api.interface.authentication_strategy_interface import AuthenticationStrategyInterface

# Internals
from conjur_api.models import SslVerificationMode, CreateHostData, CreateTokenData, ListMembersOfData, \
ListPermittedRolesData, ConjurConnectionInfo, Resource

from conjur_api.errors.errors import ResourceNotFoundException, MissingRequiredParameterException, HttpStatusError
from conjur_api.interface.credentials_store_interface import CredentialsProviderInterface
from conjur_api.http.api import Api
from conjur_api.utils.decorators import allow_sync_invocation

Expand All @@ -41,15 +41,15 @@ def __init__(
self,
connection_info: ConjurConnectionInfo,
ssl_verification_mode: SslVerificationMode = SslVerificationMode.TRUST_STORE,
credentials_provider: CredentialsProviderInterface = None,
authn_strategy: AuthenticationStrategyInterface = None,
debug: bool = False,
http_debug: bool = False,
async_mode: bool = True):
"""
@param conjurrc_data: Connection metadata for conjur server
@param ssl_verification_mode: Certificate validation stratagy
@param credentials_provider:
@param authn_strategy:
@param debug:
@param http_debug:
@param async_mode: This will make all of the class async functions run in sync mode (without need of await)
Expand All @@ -69,7 +69,7 @@ def __init__(
self.ssl_verification_mode = ssl_verification_mode
self.connection_info = connection_info
self.debug = debug
self._api = self._create_api(http_debug, credentials_provider)
self._api = self._create_api(http_debug, authn_strategy)

logging.debug("Client initialized")

Expand Down Expand Up @@ -236,16 +236,12 @@ async def find_resource_by_identifier(self, resource_identifier: str) -> list:

return resources[0]

def _create_api(self, http_debug, credentials_provider):

credential_location = credentials_provider.get_store_location()
logging.debug("Attempting to retrieve credentials from the '%s'...", credential_location)
logging.debug("Successfully retrieved credentials from the '%s'", credential_location)
def _create_api(self, http_debug, authn_strategy):

return Api(
connection_info=self.connection_info,
ssl_verification_mode=self.ssl_verification_mode,
credentials_provider=credentials_provider,
authn_strategy=authn_strategy,
debug=self.debug,
http_debug=http_debug)

Expand Down
74 changes: 13 additions & 61 deletions conjur_api/http/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# Internals
from conjur_api.http.endpoints import ConjurEndpoint
from conjur_api.interface.credentials_store_interface import CredentialsProviderInterface
from conjur_api.interface.authentication_strategy_interface import AuthenticationStrategyInterface
from conjur_api.wrappers.http_response import HttpResponse
from conjur_api.wrappers.http_wrapper import HttpVerb, invoke_endpoint
from conjur_api.errors.errors import InvalidResourceException, MissingRequiredParameterException
Expand Down Expand Up @@ -44,7 +44,7 @@ class Api:
def __init__(
self,
connection_info: ConjurConnectionInfo,
credentials_provider: CredentialsProviderInterface,
authn_strategy: AuthenticationStrategyInterface,
ssl_verification_mode: SslVerificationMode = SslVerificationMode.TRUST_STORE,
debug: bool = False,
http_debug=False,
Expand All @@ -55,8 +55,7 @@ def __init__(

self._account = connection_info.conjur_account
self._url = connection_info.conjur_url
self._api_key = None
self.credentials_provider: CredentialsProviderInterface = credentials_provider
self.authn_strategy: AuthenticationStrategyInterface = authn_strategy
self.debug = debug
self.http_debug = http_debug
self.api_token_expiration = None
Expand All @@ -71,15 +70,6 @@ def __init__(
# from .http import enable_http_logging
# if http_debug: enable_http_logging()

@property
def api_key(self) -> str:
"""
Property returns api_key. if no api_key we try the password as sometimes
@return: api_key
"""
# TODO do not use password after credentials store is fixed to also store API key
return self._api_key or self.password

@property
# pylint: disable=missing-docstring
async def api_token(self) -> str:
Expand All @@ -96,67 +86,29 @@ async def api_token(self) -> str:
logging.debug("Using cached API token...")
return self._api_token

@property
def password(self) -> str:
"""
password as being saved inside credentials_provider
@return:
"""
return self.credentials_provider.load(self._url).password

@property
def login_id(self) -> str:
"""
@return: The login_id (username)
"""
if not self._login_id:
self._login_id = self.credentials_provider.load(self._url).username
return self._login_id

async def login(self) -> str:
"""
This method uses the basic auth login id (username) and password
to retrieve an conjur_api key from the server that can be later used to
retrieve short-lived conjur_api tokens.
"""
logging.debug("Logging in to %s...", self._url)
password = self.password
if not password:
raise MissingRequiredParameterException("password requires when login")
response = await invoke_endpoint(HttpVerb.GET, ConjurEndpoint.LOGIN,
self._default_params, auth=(self.login_id, password),
ssl_verification_metadata=self.ssl_verification_data)
self._api_key = response.text
return self.api_key

if 'login' in self.authn_strategy:
return await self.authn_strategy.login(
ConjurConnectionInfo(self._url, self._account),
self.ssl_verification_data
)

async def authenticate(self) -> str:
"""
Authenticate uses the api_key to fetch a short-lived conjur_api token that
for a limited time will allow you to interact fully with the Conjur
vault.
"""
if not self.api_key and self.login_id and self.password:
# TODO we do this since api_key is not provided. it should be stored like username,
# password inside credentials_data
await self.login()

if not self.login_id or not self.api_key:
raise MissingRequiredParameterException("Missing parameters in "
"authentication invocation")

params = {
'login': self.login_id
}
params.update(self._default_params)

logging.debug("Authenticating to %s...", self._url)
response = await invoke_endpoint(
HttpVerb.POST,
ConjurEndpoint.AUTHENTICATE,
params,
self.api_key,
ssl_verification_metadata=self.ssl_verification_data)
return response.text
return await self.authn_strategy.authenticate(
ConjurConnectionInfo(self._url, self._account),
self.ssl_verification_data
)

async def resources_list(self, list_constraints: dict = None) -> dict:
"""
Expand Down
1 change: 1 addition & 0 deletions conjur_api/interface/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
This module holds all the exposed interfaces of the SDK
"""
from conjur_api.interface.credentials_store_interface import CredentialsProviderInterface
from conjur_api.interface.authentication_strategy_interface import AuthenticationStrategyInterface
30 changes: 30 additions & 0 deletions conjur_api/interface/authentication_strategy_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-

"""
AuthenticationStrategy Interface
This class describes a shared interface for authenticating to Conjur
"""

# Builtins
import abc
from conjur_api.models.general.conjur_connection_info import ConjurConnectionInfo
from conjur_api.models.ssl.ssl_verification_metadata import SslVerificationMetadata

# pylint: disable=too-few-public-methods
class AuthenticationStrategyInterface(metaclass=abc.ABCMeta): # pragma: no cover
"""
AuthenticationStrategyInterface
This class is an interface that outlines a shared interface for authentication strategies
"""

@abc.abstractmethod
async def authenticate(
self,
connection_info: ConjurConnectionInfo,
ssl_verification_data: SslVerificationMetadata
) -> str:
"""
Authenticate uses the api_key to fetch a short-lived conjur_api token that
for a limited time will allow you to interact fully with the Conjur
vault.
"""
1 change: 1 addition & 0 deletions conjur_api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
from conjur_api.models.hostfactory.create_host_data import CreateHostData
from conjur_api.models.ssl.ssl_verification_mode import SslVerificationMode
from conjur_api.models.general.credentials_data import CredentialsData
from conjur_api.models.enums.authn_types import AuthnTypes
21 changes: 21 additions & 0 deletions conjur_api/models/enums/authn_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
AuthnTypes module
This module is used to represent different authentication methods.
"""

from enum import Enum


class AuthnTypes(Enum): # pragma: no cover
"""
Represent possible authn methods that can be used.
"""
AUTHN = 0
# Future:
# LDAP = 1

def __str__(self):
"""
Return string representation of AuthnTypes.
"""
return self.name.lower()
1 change: 1 addition & 0 deletions conjur_api/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
This module holds all the providers of the SDK
"""
from conjur_api.providers.simple_credentials_provider import SimpleCredentialsProvider
from conjur_api.providers.authn_authentication_strategy import AuthnAuthenticationStrategy
Loading

0 comments on commit 24a1133

Please sign in to comment.