-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
enable client to create PAM credentials auth_file #517
Comments
Also interested in this change. |
The way this issue is framed, it deals with PAM credentials in particular ... so I'm wondering if the community interest exists (or should apply) to the native login case too. If so, we'd need another configuration option to store the native password - to enable PRC's automatic generation of |
you're proposing a configuration option for each login type? rather than having one that both would honor? can you propose a name for this one (or two) configuration option(s)? can we get away with having no new user-facing configuration options? |
I was hoping for an official, Pythonic way of emulating the IRODs program Here is how I have replicated the functionality of However to be able to create a valid Despite this, the I'm using import typing as t
import warnings
...
from irods.exception import iRODSException
from irods import password_obfuscation
from irods.session import iRODSSession
...
...
__GLOBAL_PAM_PW_NEGOTIATED = None
...
def iinit_via_python(
password: str,
irods_env_file: t.Union[str, Path],
ssl_ca_certificate_file: t.Union[str, Path],
):
"""
Execute the iinit command via the irods-python library.
"""
# Cast the iRODS environment file and SSL CA certificate file to strings.
irods_env_file = str(irods_env_file)
ssl_ca_certificate_file = str(ssl_ca_certificate_file)
# NB: An explict ssl certificate file kwarg will override the value in the
# irods_env_file
with get_irods_session(
password=password,
irods_env_file=irods_env_file,
ssl_ca_certificate_file=ssl_ca_certificate_file,
) as session:
token_list = session.pam_pw_negotiated
logging.info(f"Received iRODS auth token")
# Compute flags - global variable set when session is created by context
use_normal_token = isinstance(token_list, list) and len(token_list) > 0
use_global_token = (
__GLOBAL_PAM_PW_NEGOTIATED is not None
and isinstance(__GLOBAL_PAM_PW_NEGOTIATED, list)
and len(__GLOBAL_PAM_PW_NEGOTIATED) > 0
)
# Find and process the token
if use_normal_token:
token = token_list[0]
elif not use_normal_token and use_global_token:
logging.info(
"iRODS session pam_pw_negotiated was reset, "
"using frozen value from global variable"
)
token = __GLOBAL_PAM_PW_NEGOTIATED[0]
else:
raise ValueError("No token returned from iRODS.")
encoded_password = password_obfuscation.encode(token)
_persist_irods_auth_file(encoded_password)
@contextmanager
def get_irods_session(
password: t.Optional[str] = None,
irods_env_file: t.Optional[t.Union[str, Path]] = None,
ssl_ca_certificate_file: t.Optional[t.Union[str, Path]] = None,
) -> t.Generator[iRODSSession, None, None]:
"""
Get an iRODS session object within a managed context. If the iRODS environment file or SSL CA
certificate file are not provided, their default locations will be used. If
the files are not found, an error will be raised.
Raises:
MyAppError: If the iRODS session is not authenticated and connected.
Optional parameters:
- password: The iRODS password, if absent the session will try to
authenticate with an existing token.
- irods_env_file: The path to the iRODS environment file, e.g.
~/.irods/irods_environment.json. If absent, the default location will be
used.
- ssl_ca_certificate_file: The path to the SSL CA certificate file used. If
absent, the value in the irods_env_file will be used, or falling back to
the default location.
Usage:
>>> with get_irods_session() as session:
... # Perform operations with the session
... pass
"""
irods_env_file = _resolve_irods_env_file(irods_env_file)
ssl_ca_certificate_file = _resolve_ssl_ca_certificate_file(
irods_env_file, ssl_ca_certificate_file
)
# Prepare the session keyword arguments.
kwargs = {
"irods_env_file": str(irods_env_file),
"ssl_context": get_ssl_context(),
"ssl_ca_certificate_file": str(ssl_ca_certificate_file),
}
if password is not None:
kwargs["password"] = password
# Create the iRODS session and check if it is connected.
try:
session = iRODSSession(**kwargs)
try:
_freeze_global_pam_pw_negotiated(session)
yield session
finally:
session.cleanup()
except iRODSException as err:
... # custom handling
raise
except Exception as err:
... # no special handling
raise
return
def _freeze_global_pam_pw_negotiated(session: iRODSSession):
"""
Why does this exist? Aren't globals horrible? Yes, they are but we have challenges.
If you perform ANY query on the session object, the pam_pw_negotiated attribute will be reset and empty. So we want
to create a global variable that will store the value of pam_pw_negotiated so that we can use it later.
"""
warnings.warn("Not threadsafe! Encapsulate 'iinit_via_python' logic as a class to avoid global var to manage 'pam_pw_negotiated' or see if upstream solved problem", RuntimeWarning)
global __GLOBAL_PAM_PW_NEGOTIATED
__GLOBAL_PAM_PW_NEGOTIATED = session.pam_pw_negotiated
return
def _persist_irods_auth_file(encoded_password: str) -> None:
# Default iRODs home directory and authentication file paths.
irods_auth_file = _resolve_irods_auth_file() # An expanded user path equivalent of ~/.irods/.irodsA
irods_dir = irods_auth_file.parent
# Create the iRODS home directory if it does not exist.
if not irods_dir.exists():
logging.info(f"Creating iRODS home directory {str(irods_dir)}")
irods_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
# Write the token to the iRODS authentication file or raise an error.
logging.info(f"Persisting iRODS encoded auth token to {str(irods_auth_file)}")
irods_auth_file.unlink(missing_ok=True)
with open(irods_auth_file, "w") as auth_file:
auth_file.write(encoded_password)
os.chmod(irods_auth_file, 0o600)
return |
You mention
Can you confirm this is still necessary with |
This is possibly just a half-baked idea of mine. It's perhaps better to go with a library and/or command that is explicitly iinit-like, which some (notably @IanVermes) have expressed a wish for. (See below). There might be a way for routine login attempts to call into that library from the client's login attempts, and that is the part that would require storing cleartext passwords as user-facing configuration. Although Ian has apparently figured out how to avoid that for PAM! (Assuming we're not covering the case of a PAM password the user has modified in the database since the last session). |
@trel Sorry for the delay in answering. I tried and the above approach works in
Is this different to what one would expect from |
Yes, solid security is certainly the rule : ) . Or the aim - my attempts at uninterrupted client access via PAM (ie . not requiring a user to re-enter a new password the way that iinit/icommands would if the old PAM password is expired) have thus far involved storing the PAM user's cleartext password in a configuration variable. The security of that depends on protecting a |
… native authentication. This commit introduces iinit-like capability to generate the .irodsA file, when not previously existing, for the pam_password authentication scheme. Also, free functions are introduced which create the .irodsA file from a cleartext password value in the native and pam_password authentication schemes.
…ication. This commit introduces iinit-like capability to generate the .irodsA file, when not previously existing, for the pam_password authentication scheme. Also, free functions are introduced which create the .irodsA file from a cleartext password value in the native and pam_password authentication schemes.
We realize this might be a bit delayed, but wondering whether there are any plans to integrate the pam_interactive option into the PRC soon? |
Yes, the plan is to add support for pam_interactive to the PRC. We do not have a specific date for that, but the goal is to add it sooner rather than later. |
let's create an issue for it - and link it from here. |
Created #653 |
As of v2.0.0, the Python client is capable of renewing the PAM credentials in the client environment secrets file (.irodsA), in the event of e.g. an expired time-to-live or even if it simply did not previously exist
The text was updated successfully, but these errors were encountered: