You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
5.2 KiB
232 lines
5.2 KiB
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strconv"
|
|
|
|
"github.com/Nerzal/gocloak/v7"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
l "gitlab.com/cyclops-utilities/logging"
|
|
)
|
|
|
|
type basicUserData struct {
|
|
UserSub string
|
|
}
|
|
|
|
type userInfo struct {
|
|
Username string `json:"username"`
|
|
Firstname string `json:"firstname"`
|
|
Lastname string `json:"lastname"`
|
|
EmailAddress string `json:"email"`
|
|
EmailVerified bool `json:"emailverified"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// AuthAPIKey (Swagger func) assures that the token provided when connecting to
|
|
// the API is the one presented in the config file as the valid token for the
|
|
// deployment.
|
|
func AuthAPIKey(token string) (i interface{}, e error) {
|
|
|
|
l.Warning.Printf("[SECURITY] APIKey Authentication: Trying entry with token: %v\n", token)
|
|
|
|
if !cfg.APIKey.Enabled {
|
|
|
|
e = errors.New("the API Key authentication is not active in this deployment")
|
|
|
|
metricSecurity.With(prometheus.Labels{"mode": "APIKey", "state": "DISABLED"}).Inc()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if cfg.APIKey.Token == token {
|
|
|
|
i = basicUserData{
|
|
UserSub: token,
|
|
}
|
|
|
|
metricSecurity.With(prometheus.Labels{"mode": "APIKey", "state": "ACCESS GRANTED"}).Inc()
|
|
|
|
} else {
|
|
|
|
e = errors.New("provided token is not valid")
|
|
|
|
metricSecurity.With(prometheus.Labels{"mode": "APIKey", "state": "INVALID TOKEN"}).Inc()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// AuthKeycloak (Swagger func) is called whenever it is necessary to check if a
|
|
// token which is presented to access an API is valid.
|
|
// The functions assumes that in keycloak the roles are going to be in uppercase
|
|
// and in the form of SCOPE_ROLE.
|
|
// Example:
|
|
// ADMIN_ROLE <-> scope admin
|
|
func AuthKeycloak(token string, scopes []string) (i interface{}, returnErr error) {
|
|
|
|
l.Debug.Printf("[SECURITY] AuthKeycloak: Performing authentication check - token = %v...%v\n", token, scopes)
|
|
|
|
if !cfg.Keycloak.Enabled {
|
|
|
|
returnErr = errors.New("the Keycloak authentication is not active in this deployment")
|
|
|
|
metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "DISABLED"}).Inc()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
keycloakService := getKeycloakService(cfg.Keycloak)
|
|
client := gocloak.NewClient(keycloakService)
|
|
|
|
ctx := context.Background()
|
|
|
|
_, err := client.LoginClient(ctx, cfg.Keycloak.ClientID, cfg.Keycloak.ClientSecret, cfg.Keycloak.Realm)
|
|
// clientToken, err := client.LoginClient(ctx, cfg.Keycloak.ClientID, cfg.Keycloak.ClientSecret, cfg.Keycloak.Realm)
|
|
|
|
if err != nil {
|
|
|
|
l.Warning.Printf("[SECURITY] Problems logging in to keycloak. Error: %v\n", err.Error())
|
|
|
|
returnErr = errors.New("unable to log in to keycloak")
|
|
|
|
metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "FAIL: Keycloak Server unavailable"}).Inc()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
u, err := client.GetUserInfo(ctx, token, cfg.Keycloak.Realm)
|
|
|
|
if err != nil {
|
|
|
|
l.Warning.Printf("[SECURITY] Problems getting user Info. Error: %v\n", err.Error())
|
|
|
|
returnErr = errors.New("unable to get userInfo from keycloak")
|
|
|
|
metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "FAIL: Keycloak Server unavailable userInfo"}).Inc()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if u != nil {
|
|
|
|
i = basicUserData{
|
|
UserSub: *u.Sub,
|
|
}
|
|
|
|
metricSecurity.With(prometheus.Labels{"mode": "Keycloak", "state": "ACCESS GRANTED"}).Inc()
|
|
|
|
}
|
|
|
|
// userRoles := make(map[string]bool)
|
|
//
|
|
// roles, err := client.GetRoleMappingByUserID(ctx, clientToken.AccessToken, cfg.Keycloak.Realm, *u.Sub)
|
|
//
|
|
// if err != nil {
|
|
//
|
|
// l.Warning.Printf("[SECURITY] Problems getting roles by user ID. Error:\n" + err.Error())
|
|
//
|
|
// returnErr = errors.New("unable to retrieve user roles from keycloak")
|
|
//
|
|
// return
|
|
//
|
|
// }
|
|
//
|
|
// for _, m := range roles.RealmMappings {
|
|
//
|
|
// userRoles[*m.Name] = true
|
|
//
|
|
// }
|
|
//
|
|
// ug, err := client.GetUserGroups(ctx, clientToken.AccessToken, cfg.Keycloak.Realm, *u.Sub)
|
|
//
|
|
// if err != nil {
|
|
//
|
|
// l.Warning.Printf("[SECURITY] Problems getting groups by user ID. Error:\n" + err.Error())
|
|
//
|
|
// returnErr = errors.New("unable to get user groups from keycloak")
|
|
//
|
|
// return
|
|
//
|
|
// }
|
|
//
|
|
// for _, m := range ug {
|
|
//
|
|
// roles, err := client.GetRoleMappingByGroupID(ctx, clientToken.AccessToken, cfg.Keycloak.Realm, *m.ID)
|
|
//
|
|
// if err != nil {
|
|
//
|
|
// l.Warning.Printf("[SECURITY] Problems getting roles by group ID. Error:\n" + err.Error())
|
|
//
|
|
// returnErr = errors.New("unable get groups roles from keycloak")
|
|
//
|
|
// return
|
|
//
|
|
// }
|
|
//
|
|
// for _, n := range roles.RealmMappings {
|
|
//
|
|
// userRoles[*n.Name] = true
|
|
//
|
|
// }
|
|
//
|
|
// }
|
|
//
|
|
// control := false
|
|
//
|
|
// for _, sc := range scopes {
|
|
//
|
|
// if userRoles[strings.ToUpper(sc)+"_ROLE"] {
|
|
//
|
|
// control = true
|
|
//
|
|
// }
|
|
//
|
|
// }
|
|
//
|
|
// if !control {
|
|
//
|
|
// returnErr = errors2.New(401, "The required role is not present in the user permissions")
|
|
//
|
|
// return
|
|
//
|
|
// }
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// getKeycloaktService returns the keycloak service; note that there has to be exceptional
|
|
// handling of port 80 and port 443
|
|
func getKeycloakService(c keycloakConfig) (s string) {
|
|
|
|
if c.UseHTTP {
|
|
|
|
s = "http://" + c.Host
|
|
|
|
if c.Port != 80 {
|
|
|
|
s = s + ":" + strconv.Itoa(c.Port)
|
|
}
|
|
|
|
} else {
|
|
|
|
s = "https://" + c.Host
|
|
|
|
if c.Port != 443 {
|
|
|
|
s = s + ":" + strconv.Itoa(c.Port)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|