Skip to content

Commit

Permalink
Abstract authentication flow into new interface
Browse files Browse the repository at this point in the history
  • Loading branch information
szh committed May 25, 2022
1 parent 6a5f738 commit 370bb5b
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 31 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/cyberark-conjur-cli/compare/v8.0.0...HEAD
[8.0.0]: https://github.com/cyberark/cyberark-conjur-cli/releases/tag/v8.0.0
39 changes: 33 additions & 6 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 Down Expand Up @@ -88,34 +92,57 @@ 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

```

#### Create authentication strategy

The client also uses an authentication strategy in order to authenticate to conjur using the api_token received from the initial
login (or provided by the consuming application). This approach allows us to implement different authentication strategies
(e.g. `authn`, `authn-ldap`, `authn-k8s`) and to keep the authentication login separate from the client implementation.

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

```python

authn_provider = AuthnAuthenticationStrategy(conjur_url, account, username)

```

#### Creating the client and use it

Now that we have created `connection_info` and `credentials_provider`
We can create our client

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

client = Client(connection_info,
credentials_provider=credentials_provider,
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:

```
```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
8 changes: 6 additions & 2 deletions conjur_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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, \
Expand Down Expand Up @@ -42,6 +43,7 @@ def __init__(
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):
Expand All @@ -50,6 +52,7 @@ def __init__(
@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 +72,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, credentials_provider, authn_strategy)

logging.debug("Client initialized")

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

return resources[0]

def _create_api(self, http_debug, credentials_provider):
def _create_api(self, http_debug, credentials_provider, authn_strategy):

credential_location = credentials_provider.get_store_location()
logging.debug("Attempting to retrieve credentials from the '%s'...", credential_location)
Expand All @@ -246,6 +249,7 @@ def _create_api(self, http_debug, credentials_provider):
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
8 changes: 8 additions & 0 deletions conjur_api/errors/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ def __init__(self, message: str = "Unknown OS"):
super().__init__(self.message)


class UnknownAuthnTypeError(Exception):
""" Exception when using authentication specific logic for unknown authentication type """

def __init__(self, message: str = "Unknown authentication type"):
self.message = message
super().__init__(self.message)


class MacCertificatesError(Exception):
""" Exception when failing to get root CA certificates from keychain in mac """

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

# Internals
from conjur_api.http.endpoints import ConjurEndpoint
from conjur_api.interface.authentication_strategy_interface import AuthenticationStrategyInterface
from conjur_api.interface.credentials_store_interface import CredentialsProviderInterface
from conjur_api.wrappers.http_response import HttpResponse
from conjur_api.wrappers.http_wrapper import HttpVerb, invoke_endpoint
Expand Down Expand Up @@ -45,6 +46,7 @@ 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 @@ -57,6 +59,7 @@ def __init__(
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 Down Expand Up @@ -140,23 +143,11 @@ async def authenticate(self) -> str:
# password inside credentials_data
await self.login()

if not self.login_id or not self.api_key:
if 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(self.api_key, 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
26 changes: 26 additions & 0 deletions conjur_api/interface/authentication_strategy_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-

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

# Builtins
import abc

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, api_key: str, 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()
2 changes: 2 additions & 0 deletions conjur_api/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
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
from conjur_api.providers.authentication_strategy_factory import create_authentication_strategy
28 changes: 28 additions & 0 deletions conjur_api/providers/authentication_strategy_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-

"""
AuthenticationStrategyFactory module
This module job is to encapsulate the creation of SSLContext in the dependent of each os environment
"""

from conjur_api.errors.errors import UnknownAuthnTypeError
from conjur_api.interface.authentication_strategy_interface import AuthenticationStrategyInterface
from conjur_api.models.enums.authn_types import AuthnTypes
from conjur_api.providers.authn_authentication_strategy import AuthnAuthenticationStrategy

# pylint: disable=too-few-public-methods
def create_authentication_strategy(
authn_type: AuthnTypes,
url: str,
account: str,
username: str = None
) -> AuthenticationStrategyInterface:
"""
Factory method to create AuthenticationStrategyInterface
@return: AuthenticationStrategyInterface
"""

if authn_type == AuthnTypes.AUTHN:
return AuthnAuthenticationStrategy(url, account, username)

raise UnknownAuthnTypeError(f"Unknown authentication type '{authn_type}'")
53 changes: 53 additions & 0 deletions conjur_api/providers/authn_authentication_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
AuthnAuthenticationStrategy module
This module holds the AuthnAuthenticationStrategy class
"""

import logging
from conjur_api.errors.errors import MissingRequiredParameterException
from conjur_api.http.endpoints import ConjurEndpoint
from conjur_api.interface.authentication_strategy_interface import AuthenticationStrategyInterface
from conjur_api.wrappers.http_wrapper import HttpVerb, invoke_endpoint

# pylint: disable=too-few-public-methods
class AuthnAuthenticationStrategy(AuthenticationStrategyInterface):
"""
AuthnAuthenticationStrategy class
This class implement the "authn" strategy of authentication
"""
def __init__(
self,
url: str,
account: str,
login_id: str
):
self._url = url
self._account = account
self._login_id = login_id

async def authenticate(self, api_key: str, ssl_verification_data) -> 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 self._login_id is None:
raise MissingRequiredParameterException("login_id is required")

params = {
'url': self._url,
'account': self._account,
'login': self._login_id
}

logging.debug("Authenticating to %s...", self._url)
response = await invoke_endpoint(
HttpVerb.POST,
ConjurEndpoint.AUTHENTICATE,
params,
api_key,
ssl_verification_metadata=ssl_verification_data)
return response.text
Empty file.
Loading

0 comments on commit 370bb5b

Please sign in to comment.