Tuesday, 2024/07/23
10:00 am ET
Terrell Russell, Kory Draughn, Alan King, Harry Kodden (SURF), Claudio Cacciari (SURF)
DISCUSSION
- pam_interactive
- Account spoofing PR now merged
- Noticed responses are being recorded into
.irods/.irodsA.json
file- Including plaintext password
- New PR only saving the expire time, generated from the plugin
- If client thinks it's still valid, but the server says no
- Then it will still 'fail'
- Only a 'shortcut/hint' for the client to 'skip' a new auth attempt
- If client thinks it's still valid, but the server says no
- What breaks by NOT saving the password?
- Why did we ever write that down?
- Persistent client information
- Page 6 of https://irods.org/uploads/2022/Wolfsheimer-Cacciari-SURF-Programmable_authentication_workflows_in_iRODS-paper.pdf
- Should we provide configuration / switch to NOT save user responses?
- Need a prefix/suffix or flag to 'save' this value for each challenge/response
- 'save' or 'save' or...
- Pam interactive will store the key/values that include the prefix/suffix in the key
- 'Patch' is a list of operations coming from the pam module
- path/value... so the path would be checked for the prefix/suffix
- But pam module doesn't have 'path'... just the prompt. Only has ECHO_ON and ECHO_OFF... and we can key on that perhaps...
- If ECHO_OFF, then we should NOT write it down on client
- Need a prefix/suffix or flag to 'save' this value for each challenge/response
- Suggestion... for first release
- Let's not write anything down on client side except 'expire'
- If/when use case appears...
- we can move to ECHO_ON as flag/signal to save on client side
- Why 'expire' rather than just 'expire' or 'expiration_time'?
- Special to plugin for some reason? 'Internal'? Namespacing...
server_config.json
has some pam_interactive messages/values- 4.3.x have started using R_GRID_CONFIGURATION in the database
- Should we move pam_interactive configs into database as well?
- Seems good to have zone-wide consistency
- We already have the APIs for the server/plugin to get/set these
- Pam interactive has special logic to inspect JSON from pam module
- If prompt only, does not write it down (see page 6 of pdf above)
- If 'patch', then it records locally
- Currently, pam_unix sends NOT JSON, and plugin just defaults to write down the response... perhaps we should change this default to NOT write it down?
- Alan paste...
- We "patch state" based on the presence of these keys in the pstate JSON structure: https://github.com/irods/irods_auth_plugin_pam_interactive/blob/04bbf5b6e4ba84ee5471c15bcc6a1b1585a7baf9/plugin/src/main.cpp#L134-L149
- And here is where we construct the patch for cases where the module does not return JSON: https://github.com/irods/irods_auth_plugin_pam_interactive/blob/04bbf5b6e4ba84ee5471c15bcc6a1b1585a7baf9/plugin/src/main.cpp#L634-L662
- And here's how we write down the pstate to the file, which had all of those values unpacked into it in patch_state(): https://github.com/irods/irods_auth_plugin_pam_interactive/blob/04bbf5b6e4ba84ee5471c15bcc6a1b1585a7baf9/plugin/src/main.cpp#L440-L454
- From page 4 of pdf
"plugin_configuration": { "authentication": { "pam_interactive" : { "password_min_time": 3600, "password_max_time": 7200 } } }
- Three configurations
- pam_module somewhere, up to the administrators
- iRODS now using database-saved R_GRID_CONFIGURATION
- pam_interactive had its own in server_config...
- but now should honor the grid config
- Need to confirm we can ignore/remove this config
- ignore/delete for now, and add it back with a known use case?
- https://docs.irods.org/4.3.2/system_overview/configuration/#authentication-configuration
- OIDC/OAuth2 in HTTP API
- User mapping update
- Need plugin interface and initial implementation
- Looking at C API rather than C++
- JWT verification
- Take access token scopes into consideration
- User mapping update
- Next Meeting
- August 2024
-
Install iRODS 4.2.7 or 4.2.8 for CentOS
-
Install yum-plugin-priorities
yum install yum-plugin-priorities
(see https://wiki.centos.org/PackageManagement/Yum/Priorities)
- Configure surf yum repository
sudo vi /etc/yum.repos.d/surf-irods.repo
4.2.7
[SURF]
name=SURF
baseurl=https://artie.ia.surfsara.nl/artifactory/DMS-RPM-Testing-Public/Centos/7/irods-4.2.7/master
enabled=1
gpgcheck=0
priority=90
#Optional - if you have GPG signing keys installed, use the below flags to verify the repository metadata signature:
#gpgkey=artie.ia.surfsara.nl/artifactory/DMS-RPM-Testing-Public/7/irods-4.2.7/master/repomd.xml.key
#repo_gpgcheck=1
4.2.8
[SURF]
name=SURF
baseurl=https://artie.ia.surfsara.nl/artifactory/DMS-RPM-Testing-Public/Centos/7/irods-4.2.8/master
enabled=1
gpgcheck=0
priority=90
#Optional - if you have GPG signing keys installed, use the below flags to verify the repository metadata signature:
#gpgkey=artie.ia.surfsara.nl/artifactory/DMS-RPM-Testing-Public/7/irods-4.2.7/master/repomd.xml.key
#repo_gpgcheck=1
- clear cache
yum clean all
- Update icommands
yum update irods-icommands
- Install additional packages
yum install python-pam-module
install module (with workaround):
rpm --nodeps -i https://artie.ia.surfsara.nl/artifactory/DMS-RPM-Testing-Public/Centos/7/irods-4.2.8/master/x86_64/Packages/irods_auth_plugin_pam_interactive-0.1.0-<LATEST>.x86_64.rpm
(this is a workaround for yum install irods_auth_plugin_pam_interactive)
The auth plugin can handle conversations with an arbitrary list of challenges driven by the PAM stack. Users can trigger a conversation from a icommand (e.g. iinit). The backend sends messages / challenges to the client and recieves responses. The workflow can be dynamically branched.
By default the user's responses are stored locally after sucessful authentication. These responses are used as default values for further workflows. In a typical case, the user uses iinit to interactively communicate with the backend, while other icommands use the stored responses.
/etc/pam.d/irods
auth required pam_python.so /etc/pam.d/irods.py
/etc/pam.d/irods.py
def pam_sm_authenticate(pamh, flags, argv):
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Hello"))
msg = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_ON, "type 'more' if you want more:"))
response= msg.resp
if response == "more":
msg2 = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_ON, "type somehting:"))
pwd_msg = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "password:"))
if pwd_msg.resp == "pw":
return pamh.PAM_SUCCESS
else:
return pamh.PAM_AUTH_ERR
Alternatively, the backend can send messages that are siliently stored on the iRODS client's "cookie" file. These values can be send back to the client on demand.
/etc/pam.d/irods
auth required pam_python.so /etc/pam.d/irods_complex.py
/etc/pam.d/irods_complex.py
import json
def pam_sm_authenticate(pamh, flags, argv):
# save a simple cookie on the client
# display an optional message
imsg = json.dumps({
"patch": {"mykey": {"value": "myvalue"}},
"echo": "the cookie 'mykey' has been updated"})
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, imsg))
# get a cookie from the client
# don't bother user with this challenge
imsg = json.dumps({
"ask": "entry",
"key": "counter"})
msg = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_ON, imsg))
counter = msg.resp
with open("/out.txt", "w") as fp:
fp.write("counter:{0}".format(counter))
if not counter:
counter = 0
counter = int(counter) + 1
imsg = json.dumps({
"patch": {"counter": {"value": counter}}})
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, imsg))
# ask user for password
imsg = json.dumps({
"echo": "what is your password? ",
"key": "password",
"ask": "user"})
pwd_msg = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, imsg))
if pwd_msg.resp == "pw":
# second factor
imsg = json.dumps({
"echo": "what is your 2nd factor code? ",
"key": "second",
"ask": "user"})
code_msg = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_ON, imsg))
if code_msg.resp == "1234":
imsg = json.dumps({"echo":
"number of logins: {0}".format(counter),
"context": "all"})
# alternatives: "context": "iinit" (display message only for iinit command) (default)
# alternatives: "context": "icommand" (display message only for other icommands)
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, imsg))
# alternatively:
# pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "number of logins: {0}".format(counter)))
return pamh.PAM_SUCCESS
return pamh.PAM_AUTH_ERR