feat: add group-based role mapping for FreeIPA IAM

FreeIPA users were always assigned RoleUser regardless of their
group membership. This adds --ipa-admin-group-cn so that members
of a specified FreeIPA group are automatically granted the admin
role, and  --ipa-enable-userplus to elevate the default role to
userplus for all IPA users. The user_show RPC is updated to
request all attributes so that memberof_group is reliably
returned.
This commit is contained in:
Ben McClelland
2026-05-29 20:55:25 -07:00
parent f7cc70b157
commit 7300306886
5 changed files with 60 additions and 15 deletions
+3 -1
View File
@@ -152,6 +152,8 @@ type Opts struct {
IpaVaultName string
IpaUser string
IpaPassword string
IpaAdminGroupCN string
IpaEnableUserPlus bool
IpaInsecure bool
}
@@ -179,7 +181,7 @@ func New(o *Opts) (IAMService, error) {
o.VaultServerCert, o.VaultClientCert, o.VaultClientCertKey)
fmt.Printf("initializing Vault IAM with %q\n", o.VaultEndpointURL)
case o.IpaHost != "":
svc, err = NewIpaIAMService(o.RootAccount, o.IpaHost, o.IpaVaultName, o.IpaUser, o.IpaPassword, o.IpaInsecure)
svc, err = NewIpaIAMService(o.RootAccount, o.IpaHost, o.IpaVaultName, o.IpaUser, o.IpaPassword, o.IpaAdminGroupCN, o.IpaEnableUserPlus, o.IpaInsecure)
fmt.Printf("initializing IPA IAM with %q\n", o.IpaHost)
default:
// if no iam options selected, default to the single user mode
+26 -13
View File
@@ -49,21 +49,25 @@ type IpaIAMService struct {
vaultName string
username string
password string
adminGroupCN string
enableUserPlus bool
kraTransportKey *rsa.PublicKey
rootAcc Account
}
var _ IAMService = &IpaIAMService{}
func NewIpaIAMService(rootAcc Account, host, vaultName, username, password string, isInsecure bool) (*IpaIAMService, error) {
func NewIpaIAMService(rootAcc Account, host, vaultName, username, password, adminGroupCN string, enableUserPlus, isInsecure bool) (*IpaIAMService, error) {
ipa := IpaIAMService{
id: 0,
version: IpaVersion,
host: host,
vaultName: vaultName,
username: username,
password: password,
rootAcc: rootAcc,
id: 0,
version: IpaVersion,
host: host,
vaultName: vaultName,
username: username,
password: password,
adminGroupCN: adminGroupCN,
enableUserPlus: enableUserPlus,
rootAcc: rootAcc,
}
jar, err := cookiejar.New(nil)
if err != nil {
@@ -124,15 +128,16 @@ func (ipa *IpaIAMService) GetUserAccount(access string) (Account, error) {
return ipa.rootAcc, nil
}
req, err := ipa.newRequest("user_show/1", []string{access}, map[string]any{})
req, err := ipa.newRequest("user_show/1", []string{access}, map[string]any{"all": true})
if err != nil {
return Account{}, fmt.Errorf("ipa user_show: %w", err)
}
userResult := struct {
Gidnumber []string
Uidnumber []string
PidNumber []string
Gidnumber []string `json:"gidnumber"`
Uidnumber []string `json:"uidnumber"`
PidNumber []string `json:"pidnumber"`
MemberOfGroup []string `json:"memberof_group"`
}{}
err = ipa.rpc(req, &userResult)
@@ -153,9 +158,17 @@ func (ipa *IpaIAMService) GetUserAccount(access string) (Account, error) {
return Account{}, err
}
role := RoleUser
if ipa.enableUserPlus {
role = RoleUserPlus
}
if ipa.adminGroupCN != "" && slices.Contains(userResult.MemberOfGroup, ipa.adminGroupCN) {
role = RoleAdmin
}
account := Account{
Access: access,
Role: RoleUser,
Role: role,
UserID: uid,
GroupID: gid,
ProjectID: pId,
+16 -1
View File
@@ -82,7 +82,8 @@ var (
dogstatsServers string
ipaHost, ipaVaultName string
ipaUser, ipaPassword string
ipaInsecure bool
ipaAdminGroupCN string
ipaEnableUserPlus, ipaInsecure bool
iamDebug bool
webuiPorts []string
webuiCertFile, webuiKeyFile string
@@ -742,6 +743,18 @@ func initFlags() []cli.Flag {
EnvVars: []string{"VGW_IPA_PASSWORD"},
Destination: &ipaPassword,
},
&cli.StringFlag{
Name: "ipa-admin-group-cn",
Usage: "FreeIPA group CN whose members are granted the admin role",
EnvVars: []string{"VGW_IPA_ADMIN_GROUP_CN"},
Destination: &ipaAdminGroupCN,
},
&cli.BoolFlag{
Name: "ipa-enable-userplus",
Usage: "Grant FreeIPA users the userplus role by default instead of user",
EnvVars: []string{"VGW_IPA_ENABLE_USERPLUS"},
Destination: &ipaEnableUserPlus,
},
&cli.BoolFlag{
Name: "ipa-insecure",
Usage: "Disable verify TLS certificate of FreeIPA server",
@@ -852,6 +865,8 @@ func runGateway(ctx context.Context, be backend.Backend) error {
IpaVaultName: ipaVaultName,
IpaUser: ipaUser,
IpaPassword: ipaPassword,
IpaAdminGroupCN: ipaAdminGroupCN,
IpaEnableUserPlus: ipaEnableUserPlus,
IpaInsecure: ipaInsecure,
AccessLog: accessLog,
LogWebhookURL: logWebhookURL,
+8
View File
@@ -272,6 +272,12 @@ type Config struct {
IpaUser string
// IpaPassword is the FreeIPA password for authentication.
IpaPassword string
// IpaAdminGroupCN is the CN of the FreeIPA group whose members are granted
// the admin role. If empty, all users are assigned the user role.
IpaAdminGroupCN string
// IpaEnableUserPlus grants all FreeIPA users the userplus role by default
// instead of the user role. Members of IpaAdminGroupCN still get admin.
IpaEnableUserPlus bool
// IpaInsecure disables TLS certificate verification for the FreeIPA
// connection.
IpaInsecure bool
@@ -647,6 +653,8 @@ func RunVersityGW(ctx context.Context, be backend.Backend, cfg *Config) error {
IpaVaultName: cfg.IpaVaultName,
IpaUser: cfg.IpaUser,
IpaPassword: cfg.IpaPassword,
IpaAdminGroupCN: cfg.IpaAdminGroupCN,
IpaEnableUserPlus: cfg.IpaEnableUserPlus,
IpaInsecure: cfg.IpaInsecure,
})
if err != nil {
+7
View File
@@ -409,6 +409,13 @@ ROOT_SECRET_ACCESS_KEY=
#VGW_IPA_USER=
# Password of the user used to connect to FreeIPA
#VGW_IPA_PASSWORD=
# FreeIPA group CN whose members are granted the admin role. When set, any
# FreeIPA user that is a member of this group will be assigned the admin role.
# If unset, group membership is not checked for role assignment.
#VGW_IPA_ADMIN_GROUP_CN=
# Grant all FreeIPA users the userplus role by default instead of the user role.
# Members of VGW_IPA_ADMIN_GROUP_CN still receive the admin role regardless.
#VGW_IPA_ENABLE_USERPLUS=false
# Disable verify TLS certificate of FreeIPA server
#VGW_IPA_INSECURE=false
# FreeIPA IAM debug output