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
a6cf14d7
Verified
Commit
a6cf14d7
authored
3 years ago
by
Nik | Klampfradler
Browse files
Options
Downloads
Patches
Plain Diff
[Cache] Document module and remove some unnecessary NSS resolutions
parent
e3d51956
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/cache.rs
+92
-10
92 additions, 10 deletions
src/cache.rs
with
92 additions
and
10 deletions
src/cache.rs
+
92
−
10
View file @
a6cf14d7
...
@@ -14,6 +14,9 @@
...
@@ -14,6 +14,9 @@
* limitations under the License.
* limitations under the License.
*/
*/
//! This module encapsulates all data handling, both in-memory and
//! backed by disk storage
use
crate
::
BASE_NAME
;
use
crate
::
BASE_NAME
;
use
crate
::
unix
::{
Passwd
,
getpwnam_safe
,
getpwuid_safe
};
use
crate
::
unix
::{
Passwd
,
getpwnam_safe
,
getpwuid_safe
};
...
@@ -34,16 +37,31 @@ use serde::Serialize;
...
@@ -34,16 +37,31 @@ use serde::Serialize;
use
serde
::
de
::
DeserializeOwned
;
use
serde
::
de
::
DeserializeOwned
;
use
serde_json
;
use
serde_json
;
// FIXME move to config
const
USER_TOKEN_FILENAME
:
&
str
=
"user_token.json"
;
const
USER_TOKEN_FILENAME
:
&
str
=
"user_token.json"
;
/// Holds (partial or full) information about a user
/// This will mostly be the context user (see `Cache`), and is filled with
/// as much detail about the user as is available
pub
struct
UserInfo
<
'a
>
{
pub
struct
UserInfo
<
'a
>
{
/// Numeric user ID
uid
:
Option
<
uid_t
>
,
uid
:
Option
<
uid_t
>
,
/// Username as used for authentication
username
:
Option
<
String
>
,
username
:
Option
<
String
>
,
/// Passwd struct (once full getpwnam/getpwuid resolution was possible)
passwd
:
Option
<
Passwd
<
'a
>>
,
passwd
:
Option
<
Passwd
<
'a
>>
,
/// OAuth access token if freshly retrieved or known from disk backed storage
access_token
:
Option
<
BasicTokenResponse
>
access_token
:
Option
<
BasicTokenResponse
>
}
}
/// In-memory structure to hold global process state
/// Needed because the PAM and NSS components might be used chained (if NSS
/// resolution is necessary to complete PAM authentication), and we need to
/// remember state between the calls.
pub
struct
Cache
<
'a
>
{
pub
struct
Cache
<
'a
>
{
/// The user currently calling the library
/// For NSS, this will be the process owner; for PAM, this will be the user
/// logging in (after successful authentication)
pub
context_user
:
UserInfo
<
'a
>
pub
context_user
:
UserInfo
<
'a
>
}
}
...
@@ -64,14 +82,26 @@ impl Cache<'_> {
...
@@ -64,14 +82,26 @@ impl Cache<'_> {
}
}
impl
<
'a
>
UserInfo
<
'a
>
{
impl
<
'a
>
UserInfo
<
'a
>
{
/// Set the information of this user object to that of the process owner
// FIXME Move to Cache, with a from_current_user generator method here
pub
fn
set_current_user
(
&
mut
self
)
{
pub
fn
set_current_user
(
&
mut
self
)
{
self
.set_uid
(
get_original_euid
());
self
.set_uid
(
get_original_euid
());
}
}
/// Returns `true` if any of the information slots is filled
pub
fn
is_initialized
(
&
self
)
->
bool
{
pub
fn
is_initialized
(
&
self
)
->
bool
{
self
.uid
.is_some
()
||
self
.username
.is_some
()
self
.passwd
.is_some
()
||
self
.uid
.is_some
()
||
self
.username
.is_some
()
}
}
/// Try to do `getpwnam`/`getpwuid` resolution for this user
///
/// Will fill the `passwd` slot on success, or return an error if not successful.
/// This method will only attempt resolution if calling `getpwnam`/`getpwuid` is
/// currently considered safe, i.e. the `is_getpwnam_safe` flag has not been set
/// to `false`. It will be set to false if another resolution is currently running,
/// because libc will call back into our backend and we need to break the loop.
/// This means that e.g. home directory resolution is impossible during an NSS
/// backend call, because we cannot call NSS again.
fn
try_resolve
(
&
mut
self
)
->
Result
<
(),
io
::
Error
>
{
fn
try_resolve
(
&
mut
self
)
->
Result
<
(),
io
::
Error
>
{
// If we already have a full passwd struct, do nothing
// If we already have a full passwd struct, do nothing
if
self
.passwd
.is_some
()
{
if
self
.passwd
.is_some
()
{
...
@@ -108,8 +138,12 @@ impl <'a>UserInfo<'a> {
...
@@ -108,8 +138,12 @@ impl <'a>UserInfo<'a> {
}
}
}
}
/// Return the numeric user ID from either the passwd struct or the uid slot,
/// attempting NSS resolution before doing so (in case only username is filled)
pub
fn
get_uid
(
&
mut
self
)
->
Result
<
uid_t
,
io
::
Error
>
{
pub
fn
get_uid
(
&
mut
self
)
->
Result
<
uid_t
,
io
::
Error
>
{
self
.try_resolve
();
if
self
.uid
.is_none
()
&&
self
.passwd
.is_none
()
{
self
.try_resolve
();
}
match
&
self
.passwd
{
match
&
self
.passwd
{
Some
(
passwd
)
=>
Ok
(
passwd
.pw_uid
),
Some
(
passwd
)
=>
Ok
(
passwd
.pw_uid
),
None
=>
match
self
.uid
{
None
=>
match
self
.uid
{
...
@@ -119,15 +153,28 @@ impl <'a>UserInfo<'a> {
...
@@ -119,15 +153,28 @@ impl <'a>UserInfo<'a> {
}
}
}
}
/// Set the numeric user ID, clearing all mismatching fields and attepmting
/// resolution if necessary
pub
fn
set_uid
(
&
mut
self
,
uid
:
uid_t
)
{
pub
fn
set_uid
(
&
mut
self
,
uid
:
uid_t
)
{
self
.uid
=
Some
(
uid
);
self
.uid
=
Some
(
uid
);
self
.username
=
None
;
self
.passwd
=
None
;
if
self
.passwd
.is_some
()
&&
self
.passwd
.as_ref
()
.unwrap
()
.pw_uid
!=
uid
{
self
.try_resolve
();
// Invalidate passwd because UID does not match anymore
self
.passwd
=
None
;
self
.try_resolve
();
}
self
.username
=
match
&
self
.passwd
{
Some
(
p
)
=>
Some
(
p
.pw_name
.to_string
()),
None
=>
None
};
}
}
/// Return the username from either the passwd struct or the username slot,
/// attempting NSS resolution before doing so (in case only uid is filled)
pub
fn
get_username
(
&
mut
self
)
->
Result
<
String
,
io
::
Error
>
{
pub
fn
get_username
(
&
mut
self
)
->
Result
<
String
,
io
::
Error
>
{
self
.try_resolve
();
if
self
.username
.is_none
()
&&
self
.passwd
.is_none
()
{
self
.try_resolve
();
}
match
&
self
.passwd
{
match
&
self
.passwd
{
Some
(
passwd
)
=>
Ok
(
passwd
.pw_name
.to_string
()),
Some
(
passwd
)
=>
Ok
(
passwd
.pw_name
.to_string
()),
None
=>
match
&
self
.username
{
None
=>
match
&
self
.username
{
...
@@ -137,21 +184,35 @@ impl <'a>UserInfo<'a> {
...
@@ -137,21 +184,35 @@ impl <'a>UserInfo<'a> {
}
}
}
}
/// Set the username, clearing all mismatching fields and attepmting
/// resolution if necessary
pub
fn
set_username
(
&
mut
self
,
username
:
String
)
{
pub
fn
set_username
(
&
mut
self
,
username
:
String
)
{
self
.username
=
Some
(
username
);
self
.username
=
Some
(
username
);
self
.uid
=
None
;
self
.passwd
=
None
;
if
self
.passwd
.is_some
()
&&
self
.passwd
.as_ref
()
.unwrap
()
.pw_name
!=
self
.username
.as_ref
()
.unwrap
()
{
self
.try_resolve
();
// Invalidate passwd because UID does not match anymore
self
.passwd
=
None
;
self
.try_resolve
();
}
self
.uid
=
match
&
self
.passwd
{
Some
(
p
)
=>
Some
(
p
.pw_uid
),
None
=>
None
};
}
}
/// Return the home directory from the passwd slot,
/// attempting NSS resolution before doing so
pub
fn
get_home_directory
(
&
mut
self
)
->
Result
<&
str
,
io
::
Error
>
{
pub
fn
get_home_directory
(
&
mut
self
)
->
Result
<&
str
,
io
::
Error
>
{
self
.try_resolve
();
if
self
.passwd
.is_none
()
{
self
.try_resolve
();
}
match
&
self
.passwd
{
match
&
self
.passwd
{
Some
(
passwd
)
=>
Ok
(
passwd
.pw_dir
),
Some
(
passwd
)
=>
Ok
(
passwd
.pw_dir
),
None
=>
Err
(
io
::
Error
::
new
(
io
::
ErrorKind
::
InvalidInput
,
"foo"
))
None
=>
Err
(
io
::
Error
::
new
(
io
::
ErrorKind
::
InvalidInput
,
"foo"
))
}
}
}
}
/// Attempt to drop privileges to this user, by setting EUID to their user ID
fn
drop_privileges
(
&
mut
self
)
->
Result
<
uid_t
,
io
::
Error
>
{
fn
drop_privileges
(
&
mut
self
)
->
Result
<
uid_t
,
io
::
Error
>
{
let
current_euid
=
unsafe
{
let
current_euid
=
unsafe
{
geteuid
()
geteuid
()
...
@@ -183,6 +244,8 @@ impl <'a>UserInfo<'a> {
...
@@ -183,6 +244,8 @@ impl <'a>UserInfo<'a> {
}
}
}
}
/// Restore privileges to the original process owner by setting EUID to their user ID
// FIXME Move to global scope
fn
restore_privileges
(
&
self
)
{
fn
restore_privileges
(
&
self
)
{
let
current_euid
=
unsafe
{
let
current_euid
=
unsafe
{
geteuid
()
geteuid
()
...
@@ -201,13 +264,18 @@ impl <'a>UserInfo<'a> {
...
@@ -201,13 +264,18 @@ impl <'a>UserInfo<'a> {
}
}
}
}
/// Get the XDG base directories for this user
fn
get_user_xdg_base_directories
(
&
mut
self
)
->
Result
<
BaseDirectories
,
io
::
Error
>
{
fn
get_user_xdg_base_directories
(
&
mut
self
)
->
Result
<
BaseDirectories
,
io
::
Error
>
{
// Save original $HOME for later restore
// Save original $HOME for later restore
let
saved_home
=
env
::
var_os
(
"HOME"
);
let
saved_home
=
env
::
var_os
(
"HOME"
);
// Determine user ID to find out whether we should override $HOME
// Determine user ID to find out whether we should override $HOME
// For the current user, we rely on $HOME being set to avoid a bootstrapping
// issue to get the access token for NSS resolution
let
uid
=
self
.get_uid
()
?
;
let
uid
=
self
.get_uid
()
?
;
if
uid
!=
get_original_euid
()
{
if
uid
!=
get_original_euid
()
{
// Determine home directory and override $HOME to make the XDG code return
// XDG directories for a different user
let
user_home
=
self
.get_home_directory
()
?
;
let
user_home
=
self
.get_home_directory
()
?
;
env
::
set_var
(
"HOME"
,
user_home
);
env
::
set_var
(
"HOME"
,
user_home
);
debug!
(
"Home directory for UID {} is {}"
,
uid
,
user_home
);
debug!
(
"Home directory for UID {} is {}"
,
uid
,
user_home
);
...
@@ -231,6 +299,7 @@ impl <'a>UserInfo<'a> {
...
@@ -231,6 +299,7 @@ impl <'a>UserInfo<'a> {
return
Ok
(
base_dirs
);
return
Ok
(
base_dirs
);
}
}
/// Get the full path to a cache file under our prefix in this user's XDG cache directory
fn
place_user_cache_file
(
&
mut
self
,
filename
:
String
)
->
Result
<
PathBuf
,
io
::
Error
>
{
fn
place_user_cache_file
(
&
mut
self
,
filename
:
String
)
->
Result
<
PathBuf
,
io
::
Error
>
{
match
self
.get_user_xdg_base_directories
()
{
match
self
.get_user_xdg_base_directories
()
{
Ok
(
b
)
=>
b
.place_cache_file
(
filename
),
Ok
(
b
)
=>
b
.place_cache_file
(
filename
),
...
@@ -238,11 +307,16 @@ impl <'a>UserInfo<'a> {
...
@@ -238,11 +307,16 @@ impl <'a>UserInfo<'a> {
}
}
}
}
/// Get a known access token for this user
///
/// 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
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 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 file"
);
self
.drop_privileges
()
.ok
();
self
.drop_privileges
()
.ok
();
// 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
())
{
Ok
(
path
)
=>
match
load_json
(
path
)
{
Ok
(
path
)
=>
match
load_json
(
path
)
{
Ok
(
read_token
)
=>
Some
(
read_token
),
Ok
(
read_token
)
=>
Some
(
read_token
),
...
@@ -256,11 +330,17 @@ impl <'a>UserInfo<'a> {
...
@@ -256,11 +330,17 @@ impl <'a>UserInfo<'a> {
return
&
self
.access_token
;
return
&
self
.access_token
;
}
}
/// Set the known access token for this user
///
/// This will store the token in memory in the `access_token` slot, and attempt to
/// write the token to disk afterwards
pub
fn
set_access_token
(
&
mut
self
,
token
:
BasicTokenResponse
)
->
Result
<
(),
io
::
Error
>
{
pub
fn
set_access_token
(
&
mut
self
,
token
:
BasicTokenResponse
)
->
Result
<
(),
io
::
Error
>
{
self
.access_token
=
Some
(
token
.clone
());
self
.access_token
=
Some
(
token
.clone
());
debug!
(
"Saved token for in memory"
);
debug!
(
"Saved token for in memory"
);
// Try to write user's token cache file
// Try to write user's token cache file
// 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
()
{
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
)
=>
{
...
@@ -276,6 +356,7 @@ impl <'a>UserInfo<'a> {
...
@@ -276,6 +356,7 @@ impl <'a>UserInfo<'a> {
}
}
}
}
/// Deserialize JSON stored in a file on disk
fn
load_json
<
O
:
DeserializeOwned
>
(
path
:
PathBuf
)
->
Result
<
O
,
io
::
Error
>
{
fn
load_json
<
O
:
DeserializeOwned
>
(
path
:
PathBuf
)
->
Result
<
O
,
io
::
Error
>
{
let
file
=
fs
::
File
::
open
(
path
)
?
;
let
file
=
fs
::
File
::
open
(
path
)
?
;
let
reader
=
io
::
BufReader
::
new
(
file
);
let
reader
=
io
::
BufReader
::
new
(
file
);
...
@@ -285,6 +366,7 @@ fn load_json<O: DeserializeOwned>(path: PathBuf) -> Result<O, io::Error> {
...
@@ -285,6 +366,7 @@ fn load_json<O: DeserializeOwned>(path: PathBuf) -> Result<O, io::Error> {
}
}
}
}
/// Serialize JSON to a file on disk
fn
save_json
<
O
:
Serialize
>
(
path
:
PathBuf
,
obj
:
O
)
->
Result
<
(),
io
::
Error
>
{
fn
save_json
<
O
:
Serialize
>
(
path
:
PathBuf
,
obj
:
O
)
->
Result
<
(),
io
::
Error
>
{
let
json
=
match
serde_json
::
to_string
(
&
obj
)
{
let
json
=
match
serde_json
::
to_string
(
&
obj
)
{
Ok
(
j
)
=>
j
,
Ok
(
j
)
=>
j
,
...
...
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