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
6bb37e7e
Verified
Commit
6bb37e7e
authored
3 years ago
by
Nik | Klampfradler
Browse files
Options
Downloads
Patches
Plain Diff
[NSS] Rewrite user loading to use jq instead of custom mapping config
parent
ec186a4f
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
Cargo.toml
+1
-0
1 addition, 0 deletions
Cargo.toml
README.md
+11
-0
11 additions, 0 deletions
README.md
etc/nss_pam_oidc.example.toml
+13
-9
13 additions, 9 deletions
etc/nss_pam_oidc.example.toml
src/nss.rs
+21
-86
21 additions, 86 deletions
src/nss.rs
src/oauth.rs
+33
-29
33 additions, 29 deletions
src/oauth.rs
with
79 additions
and
124 deletions
Cargo.toml
+
1
−
0
View file @
6bb37e7e
...
@@ -26,6 +26,7 @@ log = "^0.4.11"
...
@@ -26,6 +26,7 @@ log = "^0.4.11"
syslog
=
"^5.0.0"
syslog
=
"^5.0.0"
xdg
=
"^2.2.0"
xdg
=
"^2.2.0"
serde_json
=
"^1.0.64"
serde_json
=
"^1.0.64"
jq-rs
=
"^0.4.1"
[profile.release]
[profile.release]
opt-level
=
'z'
opt-level
=
'z'
...
...
This diff is collapsed.
Click to expand it.
README.md
+
11
−
0
View file @
6bb37e7e
...
@@ -108,6 +108,17 @@ from the API up to date. It handles the following data:
...
@@ -108,6 +108,17 @@ from the API up to date. It handles the following data:
*
User access tokens (using corresponding refresh tokens, if available)
*
User access tokens (using corresponding refresh tokens, if available)
*
NSS data
*
NSS data
## Installation
### Building from source
To build from source, development headers for
`libjq`
and
`libonig`
are
required. On Debian, install them with:
```
shell
sudo
apt
install
libjq-dev libonig-dev
```
## Credits
## Credits
Special thanks to mirabilos in his position as Senior Unix System Development
Special thanks to mirabilos in his position as Senior Unix System Development
...
...
This diff is collapsed.
Click to expand it.
etc/nss_pam_oidc.example.toml
+
13
−
9
View file @
6bb37e7e
...
@@ -9,13 +9,17 @@ client_secret = ""
...
@@ -9,13 +9,17 @@ client_secret = ""
[nss]
[nss]
client_id
=
"z8Oz0tG56QRo9QEPUZTs5Eda410FMiJtYxlInxKE"
client_id
=
"z8Oz0tG56QRo9QEPUZTs5Eda410FMiJtYxlInxKE"
client_secret
=
""
client_secret
=
""
passwd_url
=
"https://ticdesk-dev.teckids.org/app/nis/api/passwd/"
[nss.maps.passwd]
urls.passwd
=
"https://ticdesk-dev.teckids.org/app/nis/api/passwd/"
name
=
{
type
=
"rename"
,
value
=
"username"
}
passwd
=
{
type
=
"static"
,
value
=
"x"
}
maps.passwd
=
"""
# uid left unchanged
{
gid
=
{
type
=
"rename"
,
value
=
"primary_gid"
}
name: .username,
gecos
=
{
type
=
"static"
,
value
=
"Foo"
}
passwd: "x",
dir
=
{
type
=
"rename"
,
value
=
"home_directory"
}
uid: .uid,
shell
=
{
type
=
"rename"
,
value
=
"login_shell"
}
gid: .primary_gid,
gecos: "Foo",
dir: .home_directory,
shell: .login_shell
}
"""
This diff is collapsed.
Click to expand it.
src/nss.rs
+
21
−
86
View file @
6bb37e7e
...
@@ -23,11 +23,8 @@ use crate::cache::get_cache;
...
@@ -23,11 +23,8 @@ use crate::cache::get_cache;
use
crate
::
logging
::
setup_log
;
use
crate
::
logging
::
setup_log
;
use
crate
::
oauth
::
get_data
;
use
crate
::
oauth
::
get_data_jq
;
use
std
::
collections
::
HashMap
;
use
serde
::{
Serialize
,
Deserialize
};
use
serde_json
::
value
::
Value
;
use
std
::
fmt
;
use
std
::
convert
::
TryInto
;
use
libc
::{
getpwuid
,
geteuid
};
use
libc
::{
getpwuid
,
geteuid
};
use
std
::
ffi
::
CStr
;
use
std
::
ffi
::
CStr
;
...
@@ -35,6 +32,19 @@ use std::ffi::CStr;
...
@@ -35,6 +32,19 @@ use std::ffi::CStr;
use
libnss
::
interop
::
Response
;
use
libnss
::
interop
::
Response
;
use
libnss
::
passwd
::{
PasswdHooks
,
Passwd
};
use
libnss
::
passwd
::{
PasswdHooks
,
Passwd
};
#[derive(Serialize,
Deserialize)]
#[serde(remote
=
"Passwd"
)]
struct
PasswdDef
{
name
:
String
,
passwd
:
String
,
uid
:
libc
::
uid_t
,
gid
:
libc
::
gid_t
,
gecos
:
String
,
dir
:
String
,
shell
:
String
}
#[derive(Deserialize)]
struct
PasswdHelper
(
#[serde(with
=
"PasswdDef"
)]
Passwd
);
fn
nss_hook_prepare
()
->
Config
{
fn
nss_hook_prepare
()
->
Config
{
let
conf
=
get_config
(
None
);
let
conf
=
get_config
(
None
);
...
@@ -57,75 +67,6 @@ fn get_current_user() -> String {
...
@@ -57,75 +67,6 @@ fn get_current_user() -> String {
euser
.to_str
()
.ok
()
.unwrap
()
.to_string
()
euser
.to_str
()
.ok
()
.unwrap
()
.to_string
()
}
}
// FIXME Provide more specific error types and messages
#[derive(Debug,
Clone)]
struct
TransformMappingError
{
msg
:
String
,
field
:
String
}
impl
fmt
::
Display
for
TransformMappingError
{
fn
fmt
(
&
self
,
f
:
&
mut
fmt
::
Formatter
)
->
fmt
::
Result
{
write!
(
f
,
"Invalid mapping configuration: field={} - {}"
,
self
.field
,
self
.msg
)
}
}
fn
transform_ent
(
conf
:
&
Config
,
row
:
&
mut
HashMap
<
String
,
Value
>
,
map_name
:
&
str
)
->
Result
<
(),
TransformMappingError
>
{
let
mapping
:
HashMap
<
String
,
HashMap
<
String
,
Value
>>
=
get_optional
(
&
conf
,
&
(
"nss.maps."
.to_string
()
+
map_name
))
.unwrap_or_default
();
for
(
field
,
rule
)
in
&
mapping
{
let
type_
:
String
=
rule
.get
(
"type"
)
.ok_or
(
TransformMappingError
{
field
:
field
.to_string
(),
msg
:
"No type"
.to_string
()
})
?
.as_str
()
.unwrap
()
.to_string
();
let
value
:
&
Value
=
rule
.get
(
"value"
)
.ok_or
(
TransformMappingError
{
field
:
field
.to_string
(),
msg
:
"No value"
.to_string
()
})
?
;
if
type_
==
"static"
{
row
.insert
(
field
.to_string
(),
value
.clone
());
}
else
if
type_
==
"rename"
{
let
old_value
:
Value
=
row
.remove
(
&
value
.as_str
()
.unwrap
()
.to_string
())
.ok_or
(
TransformMappingError
{
field
:
field
.to_string
(),
msg
:
"No value to rename"
.to_string
()
})
?
;
row
.insert
(
field
.to_string
(),
old_value
);
}
else
{
return
Err
(
TransformMappingError
{
field
:
field
.to_string
(),
msg
:
(
&
(
"Unknown type "
.to_string
()
+
&
type_
))
.to_string
()
});
};
}
Ok
(())
}
fn
map_to_passwd
(
conf
:
&
Config
,
row
:
&
mut
HashMap
<
String
,
Value
>
)
->
Result
<
Passwd
,
TransformMappingError
>
{
transform_ent
(
&
conf
,
row
,
"passwd"
)
?
;
Ok
(
Passwd
{
name
:
row
.get
(
"name"
)
.ok_or
(
TransformMappingError
{
field
:
"name"
.to_string
(),
msg
:
"No value in JSON data"
.to_string
()
})
?
.as_str
()
.unwrap
()
.to_string
(),
passwd
:
row
.get
(
"passwd"
)
.ok_or
(
TransformMappingError
{
field
:
"passwd"
.to_string
(),
msg
:
"No value in JSON data"
.to_string
()
})
?
.as_str
()
.unwrap
()
.to_string
(),
uid
:
row
.get
(
"uid"
)
.ok_or
(
TransformMappingError
{
field
:
"uid"
.to_string
(),
msg
:
"No value in JSON data"
.to_string
()
})
?
.as_u64
()
.ok_or
(
TransformMappingError
{
field
:
"uid"
.to_string
(),
msg
:
"Invalid integer"
.to_string
()
})
?
.try_into
()
.or
(
Err
(
TransformMappingError
{
field
:
"uid"
.to_string
(),
msg
:
"Overflow converting to u32"
.to_string
()
}))
?
,
gid
:
row
.get
(
"gid"
)
.ok_or
(
TransformMappingError
{
field
:
"gid"
.to_string
(),
msg
:
"No value in JSON data"
.to_string
()
})
?
.as_u64
()
.ok_or
(
TransformMappingError
{
field
:
"gid"
.to_string
(),
msg
:
"Invalid integer"
.to_string
()
})
?
.try_into
()
.or
(
Err
(
TransformMappingError
{
field
:
"gid"
.to_string
(),
msg
:
"Overflow converting to u32"
.to_string
()
}))
?
,
gecos
:
row
.get
(
"gecos"
)
.ok_or
(
TransformMappingError
{
field
:
"gecos"
.to_string
(),
msg
:
"No value in JSON data"
.to_string
()
})
?
.as_str
()
.unwrap
()
.to_string
(),
dir
:
row
.get
(
"dir"
)
.ok_or
(
TransformMappingError
{
field
:
"dir"
.to_string
(),
msg
:
"No value in JSON data"
.to_string
()
})
?
.as_str
()
.unwrap
()
.to_string
(),
shell
:
row
.get
(
"shell"
)
.ok_or
(
TransformMappingError
{
field
:
"shell"
.to_string
(),
msg
:
"No value in JSON data"
.to_string
()
})
?
.as_str
()
.unwrap
()
.to_string
(),
})
}
struct
OidcPasswd
;
struct
OidcPasswd
;
impl
PasswdHooks
for
OidcPasswd
{
impl
PasswdHooks
for
OidcPasswd
{
...
@@ -143,20 +84,14 @@ impl PasswdHooks for OidcPasswd {
...
@@ -143,20 +84,14 @@ impl PasswdHooks for OidcPasswd {
}
}
};
};
let
mut
data
:
Vec
<
H
as
hMap
<
String
,
Value
>
>
=
match
get_data
(
&
conf
,
"nss"
,
"passwd"
,
token
,
""
)
{
let
data
:
Vec
<
P
as
swdHelper
>
=
match
get_data
_jq
(
&
conf
,
"nss"
,
"passwd"
,
token
,
true
)
{
Ok
(
d
)
=>
d
,
Ok
(
d
)
=>
d
,
Err
(
_
)
=>
return
Response
::
Unavail
Err
(
_
)
=>
{
error!
(
"Could not load JSON data for passwd"
);
return
Response
::
Unavail
;
}
};
};
Response
::
Success
(
data
.into_iter
()
.map
(|
p
|
p
.0
)
.collect
())
let
mut
passwd_vec
:
Vec
<
Passwd
>
=
Vec
::
new
();
for
row
in
&
mut
data
{
match
map_to_passwd
(
&
conf
,
row
)
{
Ok
(
p
)
=>
passwd_vec
.push
(
p
),
Err
(
e
)
=>
error!
(
"Error converting JSON to passwd entry: {}"
,
e
)
};
}
Response
::
Success
(
passwd_vec
)
}
}
fn
get_entry_by_uid
(
uid
:
libc
::
uid_t
)
->
Response
<
Passwd
>
{
fn
get_entry_by_uid
(
uid
:
libc
::
uid_t
)
->
Response
<
Passwd
>
{
...
...
This diff is collapsed.
Click to expand it.
src/oauth.rs
+
33
−
29
View file @
6bb37e7e
...
@@ -37,29 +37,32 @@ use oauth2::basic::{
...
@@ -37,29 +37,32 @@ use oauth2::basic::{
};
};
use
oauth2
::
reqwest
::
http_client
;
use
oauth2
::
reqwest
::
http_client
;
use
std
::
error
;
use
serde
::
Deserialize
;
use
serde
::
Deserialize
;
use
reqwest
;
use
reqwest
;
fn
full_key
(
prefix
:
&
str
,
key
:
&
str
)
->
String
{
use
serde_json
;
let
parts
=
vec!
[
prefix
.to_string
(),
key
.to_string
()];
use
jq_rs
;
let
full_key
=
parts
.join
(
"."
);
return
full_key
;
fn
full_key
(
parts
:
Vec
<&
str
>
)
->
String
{
parts
.join
(
"."
)
}
}
fn
get_client
<
E
:
Copy
>
(
conf
:
Config
,
prefix
:
&
str
,
error_value
:
E
)
->
Result
<
BasicClient
,
E
>
{
fn
get_client
<
E
:
Copy
>
(
conf
:
Config
,
prefix
:
&
str
,
error_value
:
E
)
->
Result
<
BasicClient
,
E
>
{
let
client_id
=
ClientId
::
new
(
get_or_error
(
&
conf
,
&
full_key
(
prefix
,
"client_id"
),
error_value
)
?
);
let
client_id
=
ClientId
::
new
(
get_or_error
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"client_id"
]
),
error_value
)
?
);
let
client_secret
=
match
get_optional
(
&
conf
,
&
full_key
(
prefix
,
"client_secret"
))
{
let
client_secret
=
match
get_optional
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"client_secret"
]
))
{
Some
(
v
)
=>
Some
(
ClientSecret
::
new
(
v
)),
Some
(
v
)
=>
Some
(
ClientSecret
::
new
(
v
)),
None
=>
None
,
None
=>
None
,
};
};
let
auth_url
=
match
AuthUrl
::
new
(
get_or_error
(
&
conf
,
&
full_key
(
prefix
,
"auth_url"
),
error_value
)
?
)
{
let
auth_url
=
match
AuthUrl
::
new
(
get_or_error
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"auth_url"
]
),
error_value
)
?
)
{
Ok
(
u
)
=>
u
,
Ok
(
u
)
=>
u
,
_
=>
{
_
=>
{
error!
(
"Could not parse authorization URL"
);
error!
(
"Could not parse authorization URL"
);
return
Err
(
error_value
);
return
Err
(
error_value
);
},
},
};
};
let
token_url
=
match
get_optional
(
&
conf
,
&
full_key
(
prefix
,
"token_url"
))
{
let
token_url
=
match
get_optional
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"token_url"
]
))
{
Some
(
v
)
=>
match
TokenUrl
::
new
(
v
)
{
Some
(
v
)
=>
match
TokenUrl
::
new
(
v
)
{
Ok
(
u
)
=>
Some
(
u
),
Ok
(
u
)
=>
Some
(
u
),
Err
(
_
)
=>
{
Err
(
_
)
=>
{
...
@@ -75,7 +78,7 @@ fn get_client<E: Copy>(conf: Config, prefix: &str, error_value: E) -> Result<Bas
...
@@ -75,7 +78,7 @@ fn get_client<E: Copy>(conf: Config, prefix: &str, error_value: E) -> Result<Bas
}
}
pub
fn
get_access_token_client
<
E
:
Copy
>
(
conf
:
Config
,
prefix
:
&
str
,
error_value
:
E
,
unauth_value
:
E
)
->
Result
<
BasicTokenResponse
,
E
>
{
pub
fn
get_access_token_client
<
E
:
Copy
>
(
conf
:
Config
,
prefix
:
&
str
,
error_value
:
E
,
unauth_value
:
E
)
->
Result
<
BasicTokenResponse
,
E
>
{
let
scopes
:
Vec
<
String
>
=
match
get_optional
(
&
conf
,
&
full_key
(
prefix
,
"scopes"
))
{
let
scopes
:
Vec
<
String
>
=
match
get_optional
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"scopes"
]
))
{
Some
(
v
)
=>
v
,
Some
(
v
)
=>
v
,
None
=>
vec!
[]
None
=>
vec!
[]
};
};
...
@@ -103,7 +106,7 @@ pub fn get_access_token_client<E: Copy>(conf: Config, prefix: &str, error_value:
...
@@ -103,7 +106,7 @@ pub fn get_access_token_client<E: Copy>(conf: Config, prefix: &str, error_value:
}
}
pub
fn
get_access_token_password
<
E
:
Copy
>
(
conf
:
Config
,
prefix
:
&
str
,
username
:
String
,
password
:
String
,
error_value
:
E
,
unauth_value
:
E
)
->
Result
<
BasicTokenResponse
,
E
>
{
pub
fn
get_access_token_password
<
E
:
Copy
>
(
conf
:
Config
,
prefix
:
&
str
,
username
:
String
,
password
:
String
,
error_value
:
E
,
unauth_value
:
E
)
->
Result
<
BasicTokenResponse
,
E
>
{
let
scopes
:
Vec
<
String
>
=
match
get_optional
(
&
conf
,
&
full_key
(
prefix
,
"scopes"
))
{
let
scopes
:
Vec
<
String
>
=
match
get_optional
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"scopes"
]
))
{
Some
(
v
)
=>
v
,
Some
(
v
)
=>
v
,
None
=>
vec!
[]
None
=>
vec!
[]
};
};
...
@@ -133,31 +136,32 @@ pub fn get_access_token_password<E: Copy>(conf: Config, prefix: &str, username:
...
@@ -133,31 +136,32 @@ pub fn get_access_token_password<E: Copy>(conf: Config, prefix: &str, username:
}
}
}
}
pub
fn
get_data
<
T
:
for
<
'de
>
Deserialize
<
'de
>
,
E
:
Copy
>
(
conf
:
&
Config
,
prefix
:
&
str
,
endpoint
:
&
str
,
token
:
&
BasicTokenResponse
,
error_value
:
E
)
->
Result
<
T
,
E
>
{
fn
get_data
(
conf
:
&
Config
,
prefix
:
&
str
,
endpoint
:
&
str
,
token
:
&
BasicTokenResponse
)
->
Result
<
String
,
Box
<
dyn
error
::
Error
>
>
{
let
access_token
=
token
.access_token
()
.secret
();
let
access_token
=
token
.access_token
()
.secret
();
let
endpoint_url
:
String
=
get_or_error
(
&
conf
,
&
full_key
(
prefix
,
&
(
endpoint
.to_string
()
+
"_url"
)),
error_value
)
?
;
let
endpoint_url
:
String
=
get_or_error
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"urls"
,
endpoint
]),
""
)
?
;
info
!
(
"Loading
JSON
data from {}"
,
endpoint_url
);
debug
!
(
"Loading
text
data from {}"
,
endpoint_url
);
let
client
=
reqwest
::
blocking
::
Client
::
new
();
let
client
=
reqwest
::
blocking
::
Client
::
new
();
let
res
=
match
client
Ok
(
client
.get
(
&
endpoint_url
)
.get
(
&
endpoint_url
)
.header
(
reqwest
::
header
::
AUTHORIZATION
,
format!
(
"Bearer {}"
,
access_token
))
.header
(
reqwest
::
header
::
AUTHORIZATION
,
format!
(
"Bearer {}"
,
access_token
))
.send
()
{
.send
()
?
Ok
(
r
)
=>
r
,
.text
()
?
)
Err
(
e
)
=>
{
}
error!
(
"Could not complete HTTP request: {}"
,
e
);
return
Err
(
error_value
);
}
};
let
data
=
match
res
.json
()
{
pub
fn
get_data_jq
<
T
:
for
<
'de
>
Deserialize
<
'de
>>
(
conf
:
&
Config
,
prefix
:
&
str
,
endpoint
:
&
str
,
token
:
&
BasicTokenResponse
,
multi
:
bool
)
->
Result
<
T
,
Box
<
dyn
error
::
Error
>>
{
Ok
(
d
)
=>
d
,
let
res
:
Option
<
String
>
=
get_optional
(
&
conf
,
&
full_key
(
vec!
[
prefix
,
"maps"
,
endpoint
]));
Err
(
e
)
=>
{
let
jq_code
=
match
res
{
error!
(
"Could not parse JSON response: {}"
,
e
);
Some
(
s
)
=>
match
multi
{
return
Err
(
error_value
);
true
=>
"map("
.to_string
()
+
&
s
+
")"
,
}
false
=>
s
},
None
=>
"."
.to_string
()
};
};
let
mut
jq_prog
=
jq_rs
::
compile
(
&
jq_code
)
?
;
let
data_raw
=
get_data
(
&
conf
,
prefix
,
endpoint
,
token
)
?
;
let
data_trans
=
jq_prog
.run
(
&
data_raw
)
?
;
debug!
(
"Successfully loaded JSON data from {}"
,
endpoint_url
);
Ok
(
serde_json
::
from_str
(
&
data_trans
)
?
)
return
Ok
(
data
);
}
}
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