Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
N
nss-pam-webapi
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Model registry
Operate
Terraform modules
Monitor
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
magicfelix
nss-pam-webapi
Commits
6566c56b
Verified
Commit
6566c56b
authored
4 years ago
by
Nik | Klampfradler
Browse files
Options
Downloads
Patches
Plain Diff
[PAM] Implement token persistence in /run, and configurable persistence
parent
1ddf8c62
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
etc/nss_pam_webapi.example.toml
+9
-0
9 additions, 0 deletions
etc/nss_pam_webapi.example.toml
src/cache.rs
+73
-12
73 additions, 12 deletions
src/cache.rs
src/config.rs
+2
-0
2 additions, 0 deletions
src/config.rs
src/pam.rs
+4
-2
4 additions, 2 deletions
src/pam.rs
with
88 additions
and
14 deletions
etc/nss_pam_webapi.example.toml
+
9
−
0
View file @
6566c56b
...
@@ -19,6 +19,15 @@ token_url = "https://ticdesk-dev.teckids.org/oauth/token/"
...
@@ -19,6 +19,15 @@ token_url = "https://ticdesk-dev.teckids.org/oauth/token/"
client_id
=
"Df1cpPEBsbG64oZ1Q1L8NetH1UKNBUyA5qhxg1Zh"
client_id
=
"Df1cpPEBsbG64oZ1Q1L8NetH1UKNBUyA5qhxg1Zh"
client_secret
=
""
client_secret
=
""
# Persist the user token in the filesystem
# Possible values:
# run - Only persist to runtime directory (probably /run)
# home - Also persist to home directory
# If NSS is in use, the token MUST be persisted to at least /run, or user-only
# mode will not work.
# Defaults to only /run
persist_token
=
{
run
=
true
,
home
=
true
}
[nss]
[nss]
# Client ID and secret for acquiring OAuth tokens
# Client ID and secret for acquiring OAuth tokens
# You might want to put these into a separate file nss_pam_webapi.secret.toml!
# You might want to put these into a separate file nss_pam_webapi.secret.toml!
...
...
This diff is collapsed.
Click to expand it.
src/cache.rs
+
73
−
12
View file @
6566c56b
...
@@ -283,14 +283,25 @@ impl UserInfo {
...
@@ -283,14 +283,25 @@ impl UserInfo {
}
}
}
}
/// Get the full path to a cache file under our prefix in this user's XDG runtime directory
fn
place_user_runtime_file
(
&
mut
self
,
filename
:
String
)
->
Result
<
PathBuf
,
io
::
Error
>
{
match
self
.get_user_xdg_base_directories
()
{
Ok
(
b
)
=>
b
.place_runtime_file
(
filename
),
Err
(
e
)
=>
{
error!
(
"Error placing runtime file {}: {}"
,
filename
,
e
);
Err
(
e
)
}
}
}
/// Get a known access token for this user
/// Get a known access token for this user
///
///
/// This will use the in-memory token from the `access_token` slot if it is filled,
/// This will use the in-memory token from the `access_token` slot if it is filled,
/// or attempt to load a token from disk if not
/// or attempt to load a token from disk if not
pub
fn
get_access_token
(
&
mut
self
)
->
Option
<
BasicTokenResponse
>
{
pub
fn
get_access_token
(
&
mut
self
)
->
Option
<
BasicTokenResponse
>
{
// Try to load our acess token if none is known
// Try to load our acess token
from home directory
if none is known
if
self
.access_token
.is_none
()
{
if
self
.access_token
.is_none
()
{
debug!
(
"No token in memory, trying to load from file"
);
debug!
(
"No token in memory, trying to load from
cache
file"
);
self
.drop_privileges
()
.ok
();
self
.drop_privileges
()
.ok
();
// Trying to read even after failed privilege dropping is safe
// Trying to read even after failed privilege dropping is safe
self
.access_token
=
match
self
.place_user_cache_file
(
USER_TOKEN_FILENAME
.to_string
())
{
self
.access_token
=
match
self
.place_user_cache_file
(
USER_TOKEN_FILENAME
.to_string
())
{
...
@@ -303,6 +314,21 @@ impl UserInfo {
...
@@ -303,6 +314,21 @@ impl UserInfo {
restore_privileges
();
restore_privileges
();
}
}
// Try to load our acess token from runtime directory if none is known
if
self
.access_token
.is_none
()
{
debug!
(
"No token in memory, trying to load from runtime file"
);
self
.drop_privileges
()
.ok
();
// Trying to read even after failed privilege dropping is safe
self
.access_token
=
match
self
.place_user_runtime_file
(
USER_TOKEN_FILENAME
.to_string
())
{
Ok
(
path
)
=>
match
load_json
(
path
)
{
Ok
(
read_token
)
=>
Some
(
read_token
),
Err
(
_
)
=>
None
},
Err
(
_
)
=>
None
};
restore_privileges
();
}
match
&
self
.access_token
{
match
&
self
.access_token
{
Some
(
t
)
=>
Some
(
t
.clone
()),
Some
(
t
)
=>
Some
(
t
.clone
()),
None
=>
None
None
=>
None
...
@@ -312,20 +338,53 @@ impl UserInfo {
...
@@ -312,20 +338,53 @@ impl UserInfo {
/// Set the known access token for this user
/// Set the known access token for this user
///
///
/// This will store the token in memory in the `access_token` slot, and attempt to
/// This will store the token in memory in the `access_token` slot, and attempt to
/// write the token to disk afterwards
/// write the token to disk afterwards if requested.
pub
fn
set_access_token
(
&
mut
self
,
token
:
BasicTokenResponse
,
persist
:
bool
)
->
Result
<
(),
io
::
Error
>
{
///
/// Arguments
/// ---------
///
/// * `token` - the OAuth token to store
/// * `persist_run` - whether to store token in XDG_RUNTIME_DIR (probably /run/<uid>)
/// * `persist_home` - whether to store token in XDG_CACHE_DIR (probably ~/.cache)
pub
fn
set_access_token
(
&
mut
self
,
token
:
BasicTokenResponse
,
persist_run
:
bool
,
persist_home
:
bool
)
->
Result
<
(),
io
::
Error
>
{
self
.access_token
=
Some
(
token
.clone
());
self
.access_token
=
Some
(
token
.clone
());
debug!
(
"Saved token in memory"
);
debug!
(
"Saved token in memory"
);
if
persist
{
if
persist_run
{
// Try to write user's token cache file
// Try to write user's token cache file to XDG_RUNTIME_DIR
// We need to ensure privileges were dropped successfully to avoid symlink attacks
// cf. https://capec.mitre.org/data/definitions/132.html
let
res
=
match
self
.drop_privileges
()
{
Ok
(
_
)
=>
match
self
.place_user_runtime_file
(
USER_TOKEN_FILENAME
.to_string
())
{
Ok
(
path
)
=>
{
debug!
(
"Storing token in runtime file"
);
save_json
(
path
,
&
token
)
},
Err
(
e
)
=>
{
error!
(
"Error getting cache path in runtime directory: {}"
,
e
);
Err
(
e
)
}
},
Err
(
e
)
=>
{
error!
(
"Error dropping privileges to store token in runtime directory: {}"
,
e
);
Err
(
e
)
}
};
restore_privileges
();
if
res
.is_err
()
{
return
res
;
}
}
if
persist_home
{
// Try to write user's token cache file to XDG_CACHE_DIR
// We need to ensure privileges were dropped successfully to avoid symlink attacks
// We need to ensure privileges were dropped successfully to avoid symlink attacks
// cf. https://capec.mitre.org/data/definitions/132.html
// cf. https://capec.mitre.org/data/definitions/132.html
let
res
=
match
self
.drop_privileges
()
{
let
res
=
match
self
.drop_privileges
()
{
Ok
(
_
)
=>
match
self
.place_user_cache_file
(
USER_TOKEN_FILENAME
.to_string
())
{
Ok
(
_
)
=>
match
self
.place_user_cache_file
(
USER_TOKEN_FILENAME
.to_string
())
{
Ok
(
path
)
=>
{
Ok
(
path
)
=>
{
debug!
(
"Storing token
for
in cache file"
);
debug!
(
"Storing token in cache file"
);
save_json
(
path
,
token
)
save_json
(
path
,
&
token
)
},
},
Err
(
e
)
=>
{
Err
(
e
)
=>
{
error!
(
"Error getting cache path in user home: {}"
,
e
);
error!
(
"Error getting cache path in user home: {}"
,
e
);
...
@@ -338,11 +397,13 @@ impl UserInfo {
...
@@ -338,11 +397,13 @@ impl UserInfo {
}
}
};
};
restore_privileges
();
restore_privileges
();
if
res
.is_err
()
{
res
return
res
;
}
else
{
}
Ok
(())
}
}
// If we got here, no error was ever thrown
Ok
(())
}
}
}
}
...
...
This diff is collapsed.
Click to expand it.
src/config.rs
+
2
−
0
View file @
6566c56b
...
@@ -25,6 +25,8 @@ pub fn get_config(conf_args: Option<config::Config>) -> config::Config {
...
@@ -25,6 +25,8 @@ pub fn get_config(conf_args: Option<config::Config>) -> config::Config {
// Preset default configuration
// Preset default configuration
let
mut
conf
=
config
::
Config
::
default
();
let
mut
conf
=
config
::
Config
::
default
();
conf
.set
(
"pam.flow"
,
"password"
)
.ok
();
conf
.set
(
"pam.flow"
,
"password"
)
.ok
();
conf
.set
(
"pam.persist_token.run"
,
true
)
.ok
();
conf
.set
(
"pam.persist_token.home"
,
false
)
.ok
();
// Unwrap passed arguments or use empty fallback
// Unwrap passed arguments or use empty fallback
let
conf_args
=
conf_args
.unwrap_or_default
();
let
conf_args
=
conf_args
.unwrap_or_default
();
...
...
This diff is collapsed.
Click to expand it.
src/pam.rs
+
4
−
2
View file @
6566c56b
...
@@ -105,14 +105,16 @@ impl PamServiceModule for PamOidc {
...
@@ -105,14 +105,16 @@ impl PamServiceModule for PamOidc {
// 1. ...mark getpwnam unsafe (prevent cache code from calling it)
// 1. ...mark getpwnam unsafe (prevent cache code from calling it)
set_is_getpwnam_safe
(
false
);
set_is_getpwnam_safe
(
false
);
// 2. ...store the access token in memory
// 2. ...store the access token in memory
get_context_user
()
.set_access_token
(
t
.clone
(),
false
)
.ok
();
get_context_user
()
.set_access_token
(
t
.clone
(),
false
,
false
)
.ok
();
// 3. ...call getpwnam ourselves without having the cache object locked
// 3. ...call getpwnam ourselves without having the cache object locked
let
passwd
=
getpwnam_safe
(
username
.to_string
());
let
passwd
=
getpwnam_safe
(
username
.to_string
());
if
passwd
.is_ok
()
{
if
passwd
.is_ok
()
{
// 4. ...if getpwnam was successful, store the token again (this time,
// 4. ...if getpwnam was successful, store the token again (this time,
// modulo other errors, it will go through to $HOME)
// modulo other errors, it will go through to $HOME)
get_context_user
()
.set_passwd
(
passwd
.unwrap
());
get_context_user
()
.set_passwd
(
passwd
.unwrap
());
get_context_user
()
.set_access_token
(
t
.clone
(),
true
)
.ok
();
let
persist_run
=
conf
.get_bool
(
"pam.persist_token.run"
)
.unwrap
();
let
persist_home
=
conf
.get_bool
(
"pam.persist_token.home"
)
.unwrap
();
get_context_user
()
.set_access_token
(
t
.clone
(),
persist_run
,
persist_home
)
.ok
();
}
}
// 5. ...unlock getpwnam again (somewhat unnecessary)
// 5. ...unlock getpwnam again (somewhat unnecessary)
set_is_getpwnam_safe
(
true
);
set_is_getpwnam_safe
(
true
);
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment